home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.cs.arizona.edu
/
ftp.cs.arizona.edu.tar
/
ftp.cs.arizona.edu
/
icon
/
historic
/
v941.tgz
/
icon.v941src.tar
/
icon.v941src
/
ipl
/
gpacks
/
htetris
/
htetris.icn
< prev
next >
Wrap
Text File
|
2000-07-29
|
57KB
|
1,784 lines
############################################################################
#
# File : htetris.icn
# Author: Henrik Sandin
# Date : May 3, 1999
#
############################################################################
#
# This file is in the public domain.
#
############################################################################
#
# Implements htetris, which is a version of the game tetris.
# The interface is built using the tool VIB.
# Bricks and the game pane are represented by two dimensional matrices.
# Conceptually, the brick matrices moves on top of the pane matrix.
# At every position, a brick matrix contains information on where on the
# pane matrix it is.
# An element of a matrix correspons to a 20 by 20 pixel square on the
# game pane. The game pane is 200 pixels wide and 600 pixels high, but its
# matrix has 12 colums and 31 rows. The extra row and columns are conceptually
# outside the game pane and serves as boundaries used to determine if a brick
# can move or rotate in some situations.
# An element in the pane matrix has the value 'FILLED' if there is a colored
# square belonging to a brick permanently stuck there. Otherwise it has the
# value 'EMPTY'.
# A brick can not move onto a position on the pane corresponding to an
# element in the pane matrix that has the value 'FILLED'.
#
############################################################################
#
# Requires: keysyms.icn, brickdata.icn, matrix.icn, brickio.icn,
# editor.icn, help.icn
#
############################################################################
#
# Links: random, numbers, vsetup
#
############################################################################
link random
link numbers
link vsetup
############################################################################
#
# Global varibles used by both htetris.icn and editor.icn.
#
############################################################################
global htetris_window
global htetris_vidgets
############################################################################
#
# Global varibles used by htetris.icn only.
#
# game_pane - The game playing area, which is 200 by 600 pixels.
# next_pane - The pane showing the next brick about to come up.
# anim_pane - The area where the initial animation is performed.
# score_pane - The current score area.
# highscore_pane - The highscore area.
# level_pane - The area showing the current level of difficulty.
# The showed level is either the most recently played level
# or the most recently picked starting level.
# brick_table - A table containing the bricks currently in play.
# The keys are unique names as strings.
# next_brick - The next brick to come up in a game.
# current_matrices - List containing the four matrices of the currently
# falling brick.
# current_images - List containing the four images of the currently
# falling brick.
# pane_matrix - A 12 by 32 matrix representing the game area. There are one
# extra row (bottom) and two extra columns used as edge markers.
# top_row - The currently highest (smallest row number) non-empty row
# in the pane matrix.
# rows_completed - The number of full rows achieved in the current game.
# flip_offset - A brick-specific integer which is used to calculate the
# new top-left corner position of a brick when it is flipped.
# start_speed - The level-depending speed which the next game is going to
# start at.
# speed - The current level-depending speed.
# speed_factor - Integer used to speed up the game on a slow computer.
# score - Current score.
# highscore - Highscore so far.
# next_id - Used to construct id's for added userdefined bricks.
# editor_on - Flag determining whether the editor was started or not.
# game_on - Flag determining whether a game is currently going on.
# pause - Flag determining whether a game is paused or not.
# cheated - TRUE if the player just cheated. Reset to false after cheat.
# cheating - TRUE if a cheating brick is currently falling.
# record_highscore - FALSE if the player has cheated during the current game.
# special_keys - A list of the possible special keys availible as controls.
# current_keys - current keys to control the game.
# root - The currently active interface root (htetris or editor).
#
############################################################################
global game_pane
global next_pane
global anim_pane
global score_pane
global highscore_pane
global level_pane
global brick_table
global current_matrices
global current_images
global next_brick
global next_id
global pane_matrix
global top_row
global rows_completed
global flip_offset
global start_speed
global speed
global speed_factor
global score
global highscore
global editor_on
global game_on
global pause
global cheated
global cheating
global record_highscore
global special_keys
global current_keys
global root
$define MAX_SCORE 999999999 # Defines the maximum score.
$define MIDDLE 6 # Defines the middle column of the game pane.
$define FALSE 0
$define TRUE 1
$define EMPTY 0 # The status of a square on the game pane.
$define FILLED 1 # The status of a square on the game pane.
$define WIDTH 12 # The width of the game pane matrix.
$define HEIGHT 31 # The height of the game pane matrix.
$define RIGHT_EDGE 12 # The rightmost column of the game pane matrix.
$define BOTTOM 31 # The bottom row of the game pane matrix.
$define RIGHT 1 # Move brick to the right.
$define LEFT 2 # Move brick to the left.
$define ROTATE 3 # Rotate brick.
$define SLAM 4 # Bring brick down instantly.
$define SPEED_UP 10 # The speedup when a new level is begun.
$define THRESH_HOLD 20 # Number of rows to complete before level switch.
$define ANIM_DELAY 20 # Delay in initial animation.
$define MIN_SPEED 150 # Minimum game speed (level 1).
$define MAX_SPEED 10 # Maximum game speed (level 15).
$include "keysyms.icn"
$include "brickdata.icn"
$include "matrix.icn"
$include "brickio.icn"
$include "movement.icn"
$include "help.icn"
$include "editor.icn"
############################################################################
#
# Record: brick
# Fields: color - The color of the brick in string format.
# offset - The rotation offset of this brick.
# matrices - The four matrices of this brick.
# images - The four imagestrings of this brick.
#
# This record represents a brick and stores data to use it in a game.
# The rotation offset depends on the shape of the brick and determines
# where, relative to the current upper-left corner, the new upper-left
# corner is going to be when the brick is rotated.
# 'matrices' and 'images' are two lists containing corresponding matrices
# and image strings.
#
############################################################################
record brick( color, offset, matrices, images)
############################################################################
#
# Record: position
# Fields: row_nr - Row number within the game pane matrix.
# col_nr - Column number within the game pane matrix.
# transparent - Flag determining if this square is transparent or not.
#
# This record represents the position and status of each square in a brick on
# the game pane. When a brick is falling, its matrix consists of 'position'-
# records describing where within the larger game pane matrix each one of its
# squares are positioned at the moment.
#
############################################################################
record position( row_nr, col_nr, transparent)
############################################################################
#
# Procedure: main
# Arguments: None.
# Returns : Nothing.
#
# This procedure starts the htetris application and the brick editor.
# If the brick editor could not be started properly it won't be used.
# The the event loop is entered. The htetris and the brick editor are
# "mutually exclusive". If the editor is in use, htetris does not
# accept any user events and when htetris is in use, the editor is
# not availible.
#
############################################################################
procedure main()
start_htetris()
if start_editor() then
editor_on := TRUE
else
editor_on := FALSE
repeat {
if root === htetris_vidgets["root"] then
game()
else
edit()
}
end
############################################################################
#
# Procedure: start_htetris
# Arguments: None.
# Returns : Nothing.
#
# This procedure starts the htetris application.
# Its window is opened and the different regions on the interface are
# initialized.
# Event root vidget is set to the htetris window.
# The original bricks are initialized by calling 'init_bricks' and put
# them in a global table.
# A Control keys table is created and initialized with the arrow keys.
# A global list of synonyms for valid special control keys is also
# initialized.
# Then the game pane matrix is created and various status variables used
# when playing the game are initialized.
# The score and highscore are written on the interface, the highscore
# possibly read from a file. The highscore is set to zero if the file
# could not be opened.
# The level display pane is initialized as well.
# Last of all, an initial animation is performed on the animation pane.
#
############################################################################
procedure start_htetris()
randomize()
(htetris_window := WOpen ! htetris_atts()) |
stop( "Can't open htetris window.")
htetris_vidgets := htetris( htetris_window)
game_pane := Clone( htetris_window, "bg=black",
"dx=" || htetris_vidgets["playfield"].ux,
"dy=" || htetris_vidgets["playfield"].uy)
next_pane := Clone( htetris_window,
"dx=" || htetris_vidgets["next"].ux,
"dy=" || htetris_vidgets["next"].uy)
anim_pane := Clone( htetris_window,
"dx=" || htetris_vidgets["animation"].ux,
"dy=" || htetris_vidgets["animation"].uy)
score_pane := Clone( htetris_window,
"dx=" || htetris_vidgets["score"].ux,
"dy=" || htetris_vidgets["score"].uy)
highscore_pane := Clone( htetris_window,
"dx=" || htetris_vidgets["highscore"].ux,
"dy=" || htetris_vidgets["highscore"].uy)
level_pane := Clone( htetris_window,
"dx=" || htetris_vidgets["level"].ux,
"dy=" || htetris_vidgets["level"].uy)
Clip( game_pane, 0, 0,
htetris_vidgets["playfield"].uw, htetris_vidgets["playfield"].uh)
Clip( next_pane, 0, 0,
htetris_vidgets["next"].uw, htetris_vidgets["next"].uh)
Clip( anim_pane, 0, 0,
htetris_vidgets["animation"].uw, htetris_vidgets["animation"].uh)
Clip( score_pane, 0, 0,
htetris_vidgets["score"].uw, htetris_vidgets["score"].uh)
Clip( highscore_pane, 0, 0,
htetris_vidgets["highscore"].uw, htetris_vidgets["highscore"].uh)
Clip( level_pane, 0, 0,
htetris_vidgets["level"].uw, htetris_vidgets["level"].uh)
EraseArea( game_pane)
root := htetris_vidgets["root"]
brick_table := init_bricks()
next_id := "1"
current_keys := table()
current_keys[RIGHT] := Key_Right
current_keys[LEFT] := Key_Left
current_keys[ROTATE] := Key_Up
current_keys[SLAM] := Key_Down
special_keys :=
["print screen","scroll lock","pause","insert","home","page up","end",
"page down","arrow left","arrow up","arrow right","arrow down","F1",
"F2","F3","F4","F5","F6","F7","F8","F9","F10","F11","F12","backspace",
"delete","escape","form feed","line feed","newline","return","tab",
"vertical space"]
pane_matrix := new_matrix( HEIGHT, WIDTH)
game_on := FALSE
pause := FALSE
start_speed := MIN_SPEED
speed_factor := 1
Font( level_pane, "lucidasanstypewriter-bold-24")
Font( score_pane, "lucidasanstypewriter-bold-24")
Font( highscore_pane, "lucidasanstypewriter-bold-24")
DrawString( score_pane, 2, 20, "000000000")
highscore_file := open( "highscore.dat")
if /highscore_file then {
highscore := 0
DrawString( highscore_pane, 2, 20, "000000000")
}
else if not integer( highscore_string := read( highscore_file)) |
*highscore_string > 9 then {
highscore := 0
DrawString( highscore_pane, 2, 20, "000000000")
close( highscore_file)
}
else {
highscore := integer( highscore_string)
DrawString( highscore_pane, 2, 20, right( highscore_string, 9, "0"))
close( highscore_file)
}
DrawString( level_pane, 2, 20,
right( string( (MIN_SPEED - start_speed)/10 + 1), 2, "0"))
animate()
return
end
############################################################################
#
# Procedure: close_htetris
# Arguments: None.
# Returns : Nothing.
#
# This procedure closes down the brick editor if it was started, possibly
# saving the highscore to a file, closes the htetris application window and
# exits the program altogether.
#
############################################################################
procedure close_htetris()
if editor_on = TRUE then kill_editor()
highscore_file := open( "highscore.dat", "ct")
if /highscore_file then
Notice( htetris_window,
"Could not open highscore-file, highscore unsaved.")
else
write( highscore_file, string( highscore))
close( highscore_file)
WClose( htetris_window)
exit()
end
############################################################################
#
# Procedure: game
# Arguments: None.
# Returns : Nothing.
#
# This is the game loop that plays the game.
# If the flag 'game_on' equals 'TRUE', and there are events pending, events
# corresponding to the current control keys are checked for and appropriate
# procedures are called in case of such an event. If a cheating brick is
# currently falling, move right, left and rotating will not work.
# If no control event was found, other events are processed and the current
# brick keeps falling.
# If the 'game_on' flag equals 'FALSE', events in general are processed
# and the procedure returns.
# If a certain amount of rows has been completed, the game speeds up
# ie. advances one level.
#
############################################################################
procedure game()
while game_on = TRUE do {
every 1 to ceil(speed / speed_factor) do {
if (*Pending( htetris_window) > 0) then {
event := pop( Pending())
value1 := pop( Pending())
value2 := pop( Pending())
case event of {
current_keys[RIGHT] : {
if cheating = FALSE &
can_move_right( current_matrices[1]) then
move_right( game_pane, current_matrices[1])
}
current_keys[LEFT] : {
if cheating = FALSE &
can_move_left( current_matrices[1]) then
move_left( game_pane, current_matrices[1])
}
current_keys[ROTATE] : {
if cheating = FALSE then
flip()
}
current_keys[SLAM] : {
slam()
if game_on = FALSE then break next
}
default : {
push( Pending(), value2, value1, event)
ProcessEvent( root, , shortcuts)
}
}
}
}
while pause = TRUE do ProcessEvent( root, , shortcuts)
if game_on = FALSE then next
fall()
if rows_completed > THRESH_HOLD & speed > MAX_SPEED then {
speed := speed - SPEED_UP
rows_completed := 0
EraseArea( level_pane)
DrawString( level_pane, 2, 20,
right( string( (MIN_SPEED - speed)/10 + 1), 2, "0"))
}
}
ProcessEvent( root, , shortcuts)
return
end
############################################################################
#
# Procedure: set_positions
# Arguments: matrix - Matrix to be initialized.
# first_row - Row of "background" matrix.
# first_col - Column of "background" matrix.
# Returns : matrix - Updated matrix.
#
# This procedure initializes a brick matrix with pane matrix "background"
# positions, by traversing the given matrix. The top left element is set
# to the given row, column position and all other elements are initialized
# from there.
#
############################################################################
procedure set_positions( matrix, first_row, first_col)
new_row := first_row
every r := 1 to *matrix do {
new_col := first_col
every c := 1 to *matrix[r] do {
matrix[r][c].row_nr := new_row
matrix[r][c].col_nr := new_col
new_col := new_col+1
}
new_row := new_row+1
}
return matrix
end
############################################################################
#
# Procedure: animate_brick
# Arguments: brick_rec - Data of brick to be moved.
# index - Index of matrix and image to be used.
# start_row - Start row of upper left brick square.
# start_col - Start column of upper left brick square.
# steps - The number of steps to move the brick.
# move_func - Function to move the brick with.
# Returns : Nothing.
#
# This procedure moves a given brick in the given direction the given
# number of steps on the animation pane, starting at the given position.
# The moving function can be 'move_left', 'move_right', 'move_down' or
# 'move_up'.
# Copies are made of the appropriate image and matrix which is then
# initialized.
# Although the brick matrix is initialized, there is no "background" matrix
# representing the animation pane. This is not needed since a brick is only
# to be moved a fixed number of steps and does not have to have a stop
# criterion depending on what is already on the pane.
#
############################################################################
procedure animate_brick( brick_rec, index,
start_row, start_col, steps, move_func)
current_images := [brick_rec.images[index]]
current_matrices := [copy_matrix( brick_rec.matrices[index])]
matrix := set_positions( current_matrices[1], start_row, start_col)
DrawImage( anim_pane,
(matrix[1][1].col_nr-2)*20,
(matrix[1][1].row_nr-1)*20,
current_images[1])
every 1 to steps do {
move_func( anim_pane, matrix)
WDelay( ANIM_DELAY)
}
return
end
############################################################################
#
# Procedure: animate
# Arguments: None.
# Returns : Nothing.
#
# This procedure performs an initial animation when htetris is started.
#
############################################################################
procedure animate()
animate_brick( brick_table["brick_4"], 2, 7, 15, 7, move_left)
animate_brick( brick_table["brick_7"], 1, 7, 0, 6, move_right)
animate_brick( brick_table["brick_2"], 1, -2, 7, 6, move_down)
animate_brick( brick_table["brick_1"], 1, 5, 0, 5, move_right)
animate_brick( brick_table["brick_1"], 1, 4, 15, 7, move_left)
animate_brick( brick_table["brick_6"], 2, 8, 0, 4, move_right)
animate_brick( brick_table["brick_3"], 1, 14, 8, 5, move_up)
animate_brick( brick_table["brick_5"], 1, 5, 15, 6, move_left)
animate_brick( brick_table["brick_1"], 1, 14, 5, 4, move_up)
animate_brick( brick_table["brick_7"], 1, 6, 0, 4, move_right)
animate_brick( brick_table["brick_3"], 4, 0, 10, 4, move_down)
animate_brick( brick_table["brick_2"], 1, 14, 7, 5, move_up)
animate_brick( brick_table["brick_5"], 1, 9, 15, 6, move_left)
animate_brick( brick_table["brick_3"], 2, 11, -1, 5, move_right)
animate_brick( brick_table["brick_4"], 2, 4, -1, 5, move_right)
animate_brick( brick_table["brick_2"], 2, 8, 15, 6, move_left)
animate_brick( brick_table["brick_5"], 1, 14, 8, 3, move_up)
animate_brick( brick_table["brick_6"], 2, 9, 15, 4, move_left)
animate_brick( brick_table["brick_4"], 4, 14, 10, 3, move_up)
animate_brick( brick_table["brick_1"], 1, 6, 15, 4, move_left)
shades := ["gray","dark-gray","black"]
every 1 to 3 do {
Fg( anim_pane, pop( shades))
FillRectangle( anim_pane, 120, 100, 20, 20)
WDelay( 4*ANIM_DELAY)
}
return
end
############################################################################
#
# Procedure: full_row
# Arguments: r - A row number in the game pane matrix.
# Returns : Nothing.
#
# This procedure determines if a matrix row is the game pane matrix is
# filled or not. If it's not, the procedure fails.
#
############################################################################
procedure full_row( r)
every c := 2 to 11 do
if pane_matrix[r][c] = EMPTY then
fail
return
end
############################################################################
#
# Procedure: erase_row
# Arguments: r - A row number in the game pane matrix.
# Returns : Nothing.
#
# This procedure erases the given matrix row on the game pane by drawing
# 20 consecutive black lines.
#
############################################################################
procedure erase_row( r)
first_line := (r-1)*20 # Calculate start pixel line from matrix row.
Fg( game_pane, "black")
every line := first_line to first_line+19 do {
DrawLine( game_pane, 0, line, 199, line)
WDelay()
}
return
end
############################################################################
#
# Procedure: shift_pane_matrix
# Arguments: erased_row - A row number in the game pane matrix.
# Returns : Nothing.
#
# This procedure shifts the game pane matrix by moving all rows above the
# given row up to the top row one step "down". A blank row is inserted
# as replacement for the previous top row.
#
############################################################################
procedure shift_pane_matrix( erased_row)
every r := erased_row to top_row+1 by -1 do
pane_matrix[r] := pane_matrix[r-1]
blank := list( WIDTH, EMPTY)
blank[1] := FILLED
blank[RIGHT_EDGE] := FILLED
pane_matrix[top_row] := blank
return
end
############################################################################
#
# Procedure: shift_pane
# Arguments: r - A row number in the game pane matrix.
# Returns : Nothing.
#
# This procedure shifts the game pane down graphically by copying the area
# above the given matrix row up to and including the top row, down 20 pixels
# which is the height of one row. The previous top row is erased.
#
############################################################################
procedure shift_pane( r)
upper_limit := (top_row-1)*20
CopyArea( game_pane, game_pane,
0, upper_limit, 200, (r-1)*20 - upper_limit,
0, upper_limit+20)
EraseArea( game_pane, 0, upper_limit, 200, 20)
return
end
############################################################################
#
# Procedure: add_score
# Arguments: nr_rows - Number of filled rows to get score from.
# Returns : Nothing.
#
# This procedure calculates and adds the score for the given number of
# simultanously filled rows to the total score.
# The score is 20 points per row, plus 5 bonus points for each extra row
# if there are more than one.
# The score "wraps around" at maximum score.
# The score showed on the interface is updated.
#
############################################################################
procedure add_score( nr_rows)
score := score + nr_rows*20 + (nr_rows-1)*5
if score > MAX_SCORE then
score := score - MAX_SCORE
score_string := right( score, 9, "0")
EraseArea( score_pane)
DrawString( score_pane, 2, 20, score_string)
return
end
############################################################################
#
# Procedure: eliminate_rows
# Arguments: None.
# Returns : Nothing.
#
# This procedure determines how many rows that were filled by the last
# brick to get stuck by traversing the pane matrix top-down from the top
# row to the (conceptual) bottom. For each filled row, it is erased, and the
# pane matrix and the pane are shifted.
# If there were any filled rows, the total number of completed rows is up-
# dated and points are added to the current score.
#
############################################################################
procedure eliminate_rows()
nr_full_rows := 0
every r := top_row to 30 do
if full_row( r) then {
nr_full_rows := nr_full_rows+1
erase_row( r)
shift_pane_matrix( r)
shift_pane( r)
top_row := top_row+1
}
if nr_full_rows > 0 then {
rows_completed := rows_completed + nr_full_rows
add_score( nr_full_rows)
}
return
end
############################################################################
#
# Procedure: get_stuck
# Arguments: None.
# Returns : Nothing.
#
# This procedure makes a brick stick to the pane and eliminates any rows
# that were filled as a consequence of this.
# If the position of the upper left square of the brick is higher than the
# current top row, the top row is updated.
# Then, for each element in the brick's matrix (which holds the position
# it is occupying in the pane matrix) the corresponding element in the
# pane matrix is set to the value 'FILLED'. This 'glues' the brick to the
# pane graphically and is reflected in the pane matrix.
#
############################################################################
procedure get_stuck()
matrix := current_matrices[1]
if matrix[1][1].row_nr < top_row then
top_row := matrix[1][1].row_nr
every r := 1 to *matrix do
every c := 1 to *matrix[r] do
if matrix[r][c].transparent = FALSE then
pane_matrix[matrix[r][c].row_nr][matrix[r][c].col_nr] := FILLED
eliminate_rows()
cheating := FALSE
return
end
############################################################################
#
# Procedure: create_cheat_matrix
# Arguments: None.
# Returns : Nothing.
#
# This procedure creates and returns a matrix representing a "cheat brick".
# This brick covers every empty square upto and one row above 'top row'.
# Only vertically connected empty squares are considered.
# The matrix is initialized with the appropriate game pane matrix positions.
#
############################################################################
procedure create_cheat_matrix()
cheat_string := ";1111111111"
done := FALSE
r := top_row
while done = FALSE do {
temp := ";"
every c := 2 to 11 do
if pane_matrix[r][c] = EMPTY &
cheat_string[(11*(r-top_row))+c] = 1 then
temp := temp || "1"
else
temp := temp || "0"
if temp == ";0000000000" then
done := TRUE
else {
cheat_string := cheat_string || temp
r := r+1
}
}
cheat_matrix := stom( string( r-top_row+1) || ",10" || cheat_string)
return set_positions( init_positions( cheat_matrix), 1, 2)
end
############################################################################
#
# Procedure: cheat
# Arguments: None.
# Returns : Nothing.
#
# This procedure sets 'current_matrices' and 'current_images' to the matrix
# and image of a dynamicly created "cheat brick" by creating a hidden window
# and draw the "cheat brick" in it by using the matrix and then transform it
# into a transparent imagestring.
#
############################################################################
procedure cheat()
cheat_matrix := create_cheat_matrix()
if /(cheat_window := WOpen( "canvas=hidden", "bg=black",
"width=" || (*cheat_matrix[1])*20,
"height=" || (*cheat_matrix)*20)) then
write( "No cheating today, sucker!")
else {
old_pointer := WAttrib( htetris_window, "pointer")
if old_pointer == "left ptr" then
WAttrib( htetris_window, "pointer=watch")
else
WAttrib( htetris_window, "pointer=wait")
every r := 1 to *cheat_matrix do
every c := 1 to *cheat_matrix[r] do
if cheat_matrix[r][c].transparent = EMPTY then
draw_square( r, c, cheat_window, "gray")
current_matrices := [cheat_matrix,
cheat_matrix,
cheat_matrix,
cheat_matrix]
cheat_image :=
transparentify( Capture( cheat_window, "c1", 0, 0,
WAttrib( cheat_window, "width"),
WAttrib( cheat_window, "height")))
current_images := [cheat_image,
cheat_image,
cheat_image,
cheat_image]
WClose( cheat_window)
WAttrib( htetris_window, "pointer=" || old_pointer)
}
return
end
############################################################################
#
# Procedure: fetch_next
# Arguments: None.
# Returns : Nothing.
#
# This procedure fetches the next upcoming brick by setting the current
# matrices and images to those of the next brick.
# If the user has cheated, a dynamicly created "cheat brick" is fetched
# instead of the regular one which is fetched at the next call to
# 'fetch_next' providing the user did not cheat again.
# If the user hasn't cheated, the global variable 'next_brick' is updated
# with a randomly picked brick from the global brick table and that one is
# displayed on the "next pane".
# The start positions of every square of the next brick is checked against
# the pane matrix and if it is to be placed so that any filled square in it
# will cover a position in the pane matrix which value is 'FILLED' (another
# already stuck brick resides there) the game is over.
# Even when cheating the game might be over if a brick is stuck so that its
# top row is in the first row of the game pane because a cheating brick
# always has at least one row ten squares wide.
# If the game is over the highscore is possibly updated depending if the
# user cheated or not, the game pane is cleared and the procedure returns.
# If the game is not over, the next brick is drawn in its initial position.
#
############################################################################
procedure fetch_next()
if cheated = TRUE then {
cheated := FALSE
cheat()
cheating := TRUE
}
else {
current_matrices := copy_matrices( next_brick.matrices)
current_images := copy( next_brick.images)
flip_offset := next_brick.offset
next_brick := ?brick_table
width := *(next_brick.matrices[1][1])
height := *(next_brick.matrices[1])
if width % 2 = 0 then
startx := (MIDDLE - width/2 - 1)*20
else
startx := (MIDDLE - width/2 - 2)*20
if height % 2 = 0 then
starty := (MIDDLE - height/2 - 1)*20
else
starty := (MIDDLE - height/2 - 2)*20
EraseArea( next_pane)
DrawImage( next_pane, startx, starty, next_brick.images[1])
}
matrix := current_matrices[1]
every r := 1 to *matrix do
every c := 1 to *matrix[r] do
if matrix[r][c].transparent = FALSE &
pane_matrix[matrix[r][c].row_nr][matrix[r][c].col_nr] =
FILLED then {
if score > highscore & record_highscore = TRUE then {
highscore := score
EraseArea( highscore_pane)
DrawString( highscore_pane, 2, 20,
right( string( highscore), 9, "0"))
}
game_on := FALSE
black_out()
EraseArea( next_pane)
return
}
startx := (current_matrices[1][1][1].col_nr - 2)*20
DrawImage( game_pane, startx, 0, current_images[1])
return
end
############################################################################
#
# Procedure: init_pane_matrix
# Arguments: None.
# Returns : Nothing.
#
# This procedure initializes the game pane matrix.
# The leftmost and rightmost as well as the bottom row get all their
# elements set to 'FILLED'. This row and columns are conceptually "outside"
# the actual pane. This is convenient to make the falling bricks not to go
# off the pane graphically.
# All "interior" elements within the u-shaped border of 'FILLED' elements
# are set to 'EMPTY'.
#
############################################################################
procedure init_pane_matrix()
every r := 1 to HEIGHT do
every c := 1 to WIDTH do
if r = BOTTOM | c = 1 | c = RIGHT_EDGE then
pane_matrix[r][c] := FILLED
else
pane_matrix[r][c] := EMPTY
return
end
############################################################################
#
# Procedure: black_out
# Arguments: None.
# Returns : Nothing.
#
# This procedure blanks out the game pane by drawing smaller and smaller
# gray and black rectangles until the middle is reached.
# The The whole pane is erased since the last drawn gray rectangle is on
# the pane.
#
############################################################################
procedure black_out()
every x := 0 to htetris_vidgets["playfield"].uw/2 do {
Fg( game_pane, "dark-gray")
DrawRectangle( game_pane, x+1, x+1,
htetris_vidgets["playfield"].uw-2*(x+1),
htetris_vidgets["playfield"].uh-2*(x+1))
Fg( game_pane, "black")
DrawRectangle( game_pane, x, x,
htetris_vidgets["playfield"].uw-2*x,
htetris_vidgets["playfield"].uh-2*x)
WDelay( game_pane)
}
EraseArea( game_pane)
return
end
############################################################################
#
# Procedure: valid_synonym
# Arguments: key_string - A synonym for a special key.
# Returns : Nothing.
#
# This procedure determines if a given synonym corresponds to a valid
# special key.
#
############################################################################
procedure valid_synonym( key_string)
case key_string of {
special_keys[1] : return Key_PrSc
special_keys[2] : return Key_ScrollLock
special_keys[3] : return Key_Pause
special_keys[4] : return Key_Insert
special_keys[5] : return Key_Home
special_keys[6] : return Key_PgUp
special_keys[7] : return Key_End
special_keys[8] : return Key_PgDn
special_keys[9] : return Key_Left
special_keys[10] : return Key_Up
special_keys[11] : return Key_Right
special_keys[12] : return Key_Down
special_keys[13] : return Key_F1
special_keys[14] : return Key_F2
special_keys[15] : return Key_F3
special_keys[16] : return Key_F4
special_keys[17] : return Key_F5
special_keys[18] : return Key_F6
special_keys[19] : return Key_F7
special_keys[20] : return Key_F8
special_keys[21] : return Key_F9
special_keys[22] : return Key_F10
special_keys[23] : return Key_F11
special_keys[24] : return Key_F12
special_keys[25] : return "\b"
special_keys[26] : return "\d"
special_keys[27] : return "\e"
special_keys[28] : return "\f"
special_keys[29] : return "\l"
special_keys[30] : return "\n"
special_keys[31] : return "\r"
special_keys[32] : return "\t"
special_keys[33] : return "\v"
}
return
end
############################################################################
#
# Procedure: ktos
# Arguments: key_value - The value returned from a keypress event.
# Returns : Nothing.
#
# This procedure returns a string representation of the given key value.
#
############################################################################
procedure ktos( key_value)
case key_value of {
Key_PrSc : return special_keys[1]
Key_ScrollLock : return special_keys[2]
Key_Pause : return special_keys[3]
Key_Insert : return special_keys[4]
Key_Home : return special_keys[5]
Key_PgUp : return special_keys[6]
Key_End : return special_keys[7]
Key_PgDn : return special_keys[8]
Key_Left : return special_keys[9]
Key_Up : return special_keys[10]
Key_Right : return special_keys[11]
Key_Down : return special_keys[12]
Key_F1 : return special_keys[13]
Key_F2 : return special_keys[14]
Key_F3 : return special_keys[15]
Key_F4 : return special_keys[16]
Key_F5 : return special_keys[17]
Key_F6 : return special_keys[18]
Key_F7 : return special_keys[19]
Key_F8 : return special_keys[20]
Key_F9 : return special_keys[21]
Key_F10 : return special_keys[22]
Key_F11 : return special_keys[23]
Key_F12 : return special_keys[24]
}
key_string := string( key_value)
case key_string of {
"\b" : return special_keys[25]
"\d" : return special_keys[26]
"\e" : return special_keys[27]
"\f" : return special_keys[28]
"\l" : return special_keys[29]
"\n" : return special_keys[30]
"\r" : return special_keys[31]
"\t" : return special_keys[32]
"\v" : return special_keys[33]
}
return key_string
end
############################################################################
#
# Procedure: key_value
# Arguments: None.
# Returns : specials - A window.
#
# This procedure opens and returns a window containing a list of synonyms
# for valid special keys. Null is returned if the window could not be
# opened.
#
############################################################################
procedure specials_window()
if specials := WOpen( "label=htetris", "size=120,550",
"posx=" || WAttrib( htetris_window, "posx")-60,
"posy=" || WAttrib( htetris_window, "posy")+60,
"bg=gray-white") then {
Font( specials, Font( htetris_window))
DrawString( specials, 10, 20, "Special keys:")
y := 60
every special := 1 to *special_keys do {
DrawString( specials, 10, y, special_keys[special])
y := y+15
}
}
else write( "List of special keys could not be shown.")
return specials
end
############################################################################
#
# Procedure: select_keys
# Arguments: None.
# Returns : Nothing.
#
# This procedure shows a text dialog with buttons "Okay" and "Cancel", which
# prompts for new control keys to be entered. Valid keys are any charachter
# or a synonym from the 'special_keys' list.
# If one or more of the enterd values are invalid, an error message is
# shown and the dialog reappears. If cancel is pressed the dialog dis-
# appears.
# The global variables containing the current key settings are updated.
#
############################################################################
procedure select_keys()
button_pressed :=
TextDialog( htetris_window,
["Enter control keys."],
["Move right:", "Move Left:", "Rotate:", "Slam down:"],
[],
[14, 14, 14, 14])
case button_pressed of {
"Okay" : {
if *dialog_value[1] = 1 then
right_value := dialog_value[1]
else {
right_value := valid_synonym( dialog_value[1])
if /right_value then {
Notice( htetris_window,
"Invalid key specification \"" ||
dialog_value[1] ||
"\".")
select_keys()
return
}
}
if *dialog_value[2] = 1 then
left_value := dialog_value[2]
else {
left_value := valid_synonym( dialog_value[2])
if /left_value then {
Notice( htetris_window,
"Invalid key specification \"" ||
dialog_value[2] ||
"\".")
select_keys()
return
}
}
if *dialog_value[3] = 1 then
rotate_value := dialog_value[3]
else {
rotate_value := valid_synonym( dialog_value[3])
if /rotate_value then {
Notice( htetris_window,
"Invalid key specification \"" ||
dialog_value[3] ||
"\".")
select_keys()
return
}
}
if *dialog_value[4] = 1 then
slam_value := dialog_value[4]
else {
slam_value := valid_synonym( dialog_value[4])
if /slam_value then {
Notice( htetris_window,
"Invalid key specification \"" ||
dialog_value[4] ||
"\".")
select_keys()
return
}
}
current_keys[RIGHT] := right_value
current_keys[LEFT] := left_value
current_keys[ROTATE] := rotate_value
current_keys[SLAM] := slam_value
}
}
return
end
############################################################################
#
# Procedure: pick_level
# Arguments: None.
# Returns : Nothing.
#
# This procedure shows a text dialog with buttons "Okay" and "Cancel", which
# prompts for a new starting level.
# If the entered level was valid, the starting speed and the level pane
# are updated. Else, the dialog reappears until the user enters a valid
# level or presses cancel.
#
############################################################################
procedure pick_level()
if game_on = FALSE then {
button_pressed :=
TextDialog( htetris_window,
["Enter starting level (1 - 15)."],
["Level:"],
[string( (MIN_SPEED - start_speed)/10 + 1)],
[2])
case button_pressed of {
"Okay" : {
level := integer( dialog_value[1])
if /level | level < 1 | level > 15 then {
Notice( htetris_window, "Invalid level specification.")
pick_level()
return
}
start_speed := (MIN_SPEED - (level-1)*10)
EraseArea( level_pane)
DrawString( level_pane, 2, 20, right( string( level), 2, "0"))
}
}
}
return
end
############################################################################
#
# Procedure: change_speed_factor
# Arguments: None.
# Returns : Nothing.
#
# This procedure shows a text dialog with buttons "Okay" and "Cancel", which
# prompts for a new speed factor between -10 and 10. A negative number slows
# the application down while a positive number speeds it up. If 0 was entered,
# the speed factor is set to 1.
# I the entered factor was valid, the global variable 'speed_factor' is
# updated. Else, the dialog reappears until the user enters a valid speed
# factor or presses cancel.
#
############################################################################
procedure change_speed_factor()
if game_on = FALSE then {
button_pressed :=
TextDialog( htetris_window,
["Enter new speed factor (-10 - 10)."],
["Speed factor:"],
[],
[3])
case button_pressed of {
"Okay" : {
factor := dialog_value[1]
if not integer( factor) |
factor < -10 |
factor > 10 then {
Notice( htetris_window, "Invalid speed factor.")
change_speed_factor()
return
}
if factor = 0 then
speed_factor = 1
else if factor < 0 then
speed_factor := 1.0/(-factor)
else
speed_factor := factor
}
}
}
return
end
############################################################################
#
# Procedure: new_game
# Arguments: None.
# Returns : Nothing.
#
# This procedure starts a new game at the current starting speed.
# The game pane is cleared and initialized and the next brick is fetched.
# Setting the global variable 'game_on' to 'TRUE' makes the program go into the
# game loop after this procedure has returned.
#
############################################################################
procedure new_game()
EraseArea( game_pane)
EraseArea( score_pane)
EraseArea( level_pane)
DrawString( score_pane, 2, 20, "000000000")
DrawString( level_pane, 2, 20,
right( string( (MIN_SPEED - start_speed)/10 + 1), 2, "0"))
init_pane_matrix()
randomize()
speed := start_speed
rows_completed := 0
score := 0
game_on := TRUE
pause := FALSE
cheated := FALSE
cheating := FALSE
record_highscore := TRUE
top_row := BOTTOM
next_brick := ?brick_table
fetch_next()
return
end
############################################################################
#
# Procedure: stop_game
# Arguments: None.
# Returns : Nothing.
#
# This procedure stops a running game and blanks out the game pane.
# If no game is running, nothing happens.
#
############################################################################
procedure stop_game()
if game_on = FALSE then
return
game_on := FALSE
black_out()
EraseArea( next_pane)
return
end
############################################################################
#
# Procedure: pause_game
# Arguments: None.
# Returns : Nothing.
#
# This procedure pauses a running game. If the game is paused, it is resumed.
# If a game is not in progress, nothing happens.
#
############################################################################
procedure pause_game()
if game_on = TRUE then
if pause = TRUE then
pause := FALSE
else
pause := TRUE
return
end
############################################################################
#
# Procedure: add_brick
# Arguments: None.
# Returns : Nothing.
#
# This procedure prompts for a brick to be opened from file and adds it
# to the currently used bricks. The opened brick gets a unique id which is
# used if the user wants to remove it or display it.
# If a game is in progress, nothing happens.
#
############################################################################
procedure add_brick()
if game_on = FALSE then {
if /(added := open_brick( htetris_window)) then
return
added.matrices[1] := init_positions( added.matrices[1])
added.matrices[2] := init_positions( added.matrices[2])
added.matrices[3] := init_positions( added.matrices[3])
added.matrices[4] := init_positions( added.matrices[4])
matrix := added.matrices[1]
if *matrix = *matrix[1] then
added.offset := 0
else if *matrix > *matrix[1] then
added.offset := ceil( abs( *matrix-*matrix[1])/2)
else
added.offset := -(ceil( abs( *matrix-*matrix[1])/2))
brick_table["user_" || next_id] := added
Notice( htetris_window,
"Brick successfully added.",
"Brick id is 'user_" || next_id ||"'.")
next_id := string( integer( next_id) + 1)
}
return
end
############################################################################
#
# Procedure: standard
# Arguments: None.
# Returns : Nothing.
#
# This procedure determines if a brick id entered by a user in a dialog
# is the name of one of the standard brick.
# This is a security check so that none of the original bricks get removed
# and all brick names stay unique.
#
############################################################################
procedure standard( brick_id)
standard_bricks := set( ["brick_1","brick_2","brick_3","brick_4",
"brick_5","brick_6","brick_7"])
return member( standard_bricks, brick_id)
end
############################################################################
#
# Procedure: remove_brick
# Arguments: None.
# Returns : Nothing.
#
# If there are user defined bricks in play (the total number is greater
# than seven), this procedure shows a text dialog box with buttons "Okay"
# and "Cancel", prompting the user to enter a user defined brick to be
# removed from the game.
# If no brick with the specified id is in use, the dialog reappears until
# the user enters a valid one or presses cancel.
# If a brick with the entered id is in use, it is deleted from the global
# table of bricks.
# If a game is in progress, nothing happens.
#
############################################################################
procedure remove_brick()
if game_on = FALSE then {
if *brick_table = 7 then {
Notice( htetris_window, "No user defined bricks in play.")
return
}
button_pressed :=
TextDialog( htetris_window,
["Enter id of brick to remove."],
["Id:"],
[],
[20])
case button_pressed of {
"Okay" : {
id := dialog_value[1]
if standard( id) | /brick_table[id] then {
Notice( htetris_window,
"Brick '" || id || "' is not in use.")
remove_brick()
return
}
delete( brick_table, id)
Notice( htetris_window, "Brick '" || id || "' removed.")
}
}
}
return
end
############################################################################
#
# Procedure: display_bricks
# Arguments: None.
# Returns : Nothing.
#
# If there are any user defined bricks in play, their ids are shown in a
# text dialog box with buttons "Okay" and "Cancel", prompting the user
# to enter one of the ids displayed.
# If this is done correctly, the brick corresponding to the given id is
# displayed in a popup window.
# The popup windows are open and the dialog reappears until the user
# presses cancel. Thus, several user bricks can be viewed simultanously.
# If a game is in progress, nothing happens.
#
############################################################################
procedure display_bricks()
if game_on = FALSE then {
user_bricks := ""
every user_brick := key( brick_table) do
if not standard( user_brick) then
user_bricks := user_bricks || user_brick || ","
if user_bricks == "" then {
Notice( htetris_window, "No user defined bricks in play.")
return
}
button_pressed :=
TextDialog( htetris_window,
["The following user bricks are in play:",
user_bricks,
"enter id of brick to view."],
["Id:"],
[],
[20])
case button_pressed of {
"Okay" : {
id := dialog_value[1]
if standard( id) | /brick_table[id] then {
Notice( htetris_window,
"Brick '" || id || "' is not in use.")
display_bricks()
return
}
else {
brick := brick_table[id]
temp_window :=
WOpen( "width=" || (*brick.matrices[1][1])*20,
"height=" || (*brick.matrices[1])*20,
"bg=black") | {
Notice( htetris_window,
"Image window could not be opened.")
return
}
DrawImage( temp_window, 0, 0, brick.images[1])
display_bricks()
WClose( temp_window)
return
}
}
}
}
return
end
############################################################################
#
# Procedure: edit_bricks
# Arguments: None.
# Returns : Nothing.
#
# This procedure displays the brick editor initializes it and transfers
# event handling to its window.
# No events from the htetris application window are now accepted.
# If a game is in progress, nothing happens.
#
############################################################################
procedure edit_bricks()
if game_on = FALSE then
if editor_on = TRUE then {
reset_editor( new_matrix( 3, 3), "yellow")
WAttrib( editor_window, "canvas=normal")
root := editor_vidgets["root"]
while get( Pending( editor_window))
}
return
end
############################################################################
#
# Procedure: shortcuts
# Arguments: event - An event.
# Returns : Nothing.
#
# This procedure catches and processes keyboard shortcut events.
#
############################################################################
procedure shortcuts( event)
if &meta then
case map( event) of {
"n" : new_game()
"s" : stop_game()
"p" : pause_game()
"q" : close_htetris()
"a" : add_brick()
"e" : edit_bricks()
}
return
end
################################ CALLBACKS #################################
############################################################################
#
# Procedure: game_cb
# Arguments: None.
# Returns : Nothing.
#
# This procedure handles events from the "Game" menu.
#
############################################################################
procedure game_cb( vidget, value)
case value[1] of {
"New game @N" : new_game()
"Stop game @S" : stop_game()
"Pause @P" : pause_game()
"Speed factor" : change_speed_factor()
"Pick level" : pick_level()
"Quit @Q" : close_htetris()
}
return
end
############################################################################
#
# Procedure: controls_cb
# Arguments: None.
# Returns : Nothing.
#
# This procedure handles events from the "Controls" menu.
# If the "Set keys" item was selected, a window displaying valid special
# control keys and a dialog are opened.
# If the "Current keys" item was selected, the current key settings are
# displayed in a notice dialog.
# If a game is in progress, nothing happens.
#
############################################################################
procedure controls_cb( vidget, value)
if game_on = FALSE then
case value[1] of {
"Set keys" : {
specials := specials_window()
select_keys()
if \specials then WClose( specials)
}
"Current keys" : {
Notice( htetris_window,
"Current key settings:",
"",
"Move right: " || ktos( current_keys[RIGHT]) || ".",
"Move left: " || ktos( current_keys[LEFT]) || ".",
"Rotate: " || ktos( current_keys[ROTATE]) || ".",
"Slam down: " || ktos( current_keys[SLAM]) || ".")
}
}
return
end
############################################################################
#
# Procedure: bricks_cb
# Arguments: None.
# Returns : Nothing.
#
# This procedure handles events from the "Bricks" menu.
# If a game is in progress, nothing happens.
#
############################################################################
procedure bricks_cb( vidget, value)
if game_on = FALSE then
case value[1] of {
"Add brick @A" : add_brick()
"Remove brick @R" : remove_brick()
"Bricks in use" : display_bricks()
"Brick editor @E" : edit_bricks()
}
return
end
############################################################################
#
# Procedure: htetris_help_cb
# Arguments: None.
# Returns : Nothing.
#
# This procedure handles events from the "Help" menu of the htetris
# application window.
# If a game is in progress, nothing happens.
#
############################################################################
procedure htetris_help_cb( vidget, value)
if game_on = FALSE then
case value[1] of {
"How to play" : how_to_play()
"Menus" : game_menu()
"About" : about_htetris()
}
return
end
############################################################################
#
# Procedure: buttons_cb
# Arguments: None.
# Returns : Nothing.
#
# This procedure handles events from the four convenience buttons on the
# interface.
#
############################################################################
procedure buttons_cb( vidget, value)
case vidget.id of {
"new_game" : new_game()
"stop_game" : stop_game()
"pause" : pause_game()
"quit" : close_htetris()
}
return
end
############################################################################
#
# Procedure: animation_cb
# Arguments: None.
# Returns : Nothing.
#
# This procedure handles events from the animation region.
# Only left mouse button clicks on a certain square are handled.
# If the user clicks there during a game, a cheat is going to take place
# instead of the next upcoming brick.
#
############################################################################
procedure animation_cb( vidget, event, x, y)
if game_on = TRUE then {
x := x-WAttrib( anim_pane, "dx")-1
y := y-WAttrib( anim_pane, "dy")-1
r := ctop( y)
c := ctop( x)
if (r = 6 & c = 7) then
case event of {
&lpress : {
cheated := TRUE
record_highscore := FALSE
}
}
}
return
end
#===<<vib:begin>>=== modify using vib; do not remove this marker line
procedure htetris_atts()
return ["size=520,640", "bg=gray-white", "label=htetris"]
end
procedure htetris(win, cbk)
return vsetup(win, cbk,
["htetris:Sizer:::0,0,520,640:htetris",],
["bricks:Menu:pull::100,0,50,21:Bricks",bricks_cb,
["Add brick @A","Remove brick @R","Bricks in use","Brick editor @E"]],
["controls:Menu:pull::36,0,64,21:Controls",controls_cb,
["Set keys","Current keys"]],
["game:Menu:pull::0,0,36,21:Game",game_cb,
["New game @N","Stop game @S","Pause @P","Speed factor","Pick level",
"Quit @Q"]],
["highscore_label:Label:::90,312,70,13:Highscore:",],
["htetris_help:Menu:pull::150,0,36,21:Help",htetris_help_cb,
["How to play","Menus","About"]],
["level_label:Label:::27,191,42,13:Level:",],
["menubar:Line:::0,22,520,22:",],
["new_game:Button:regular::6,30,75,30:New game",buttons_cb],
["next_label:Label:::150,30,77,13:Next brick:",],
["pause:Button:regular::6,102,75,30:Pause",buttons_cb],
["quit:Button:regular::6,138,75,30:Quit",buttons_cb],
["score_label:Label:::118,274,42,13:Score:",],
["stop_game:Button:regular::6,66,75,30:Stop game",buttons_cb],
["level:Rect:sunken::29,216,36,26:",],
["highscore:Rect:sunken::164,306,134,26:",],
["score:Rect:sunken::164,268,134,26:",],
["next:Rect:grooved::94,51,204,204:",],
["animation:Rect:invisible::25,356,260,260:",animation_cb],
["playfield:Rect:raised::310,30,204,604:",],
)
end
#===<<vib:end>>=== end of section maintained by vib