blob: 77ed7fa66c1d543d9a48156a08ac3390992779fa [file] [log] [blame]
#
# short intro
#
# game "memory": the player is supposed N pairs of pictures with the
# least possible amount of tries. We thus keep track of:
# s the current status of the pictures
# 0 backside up
# 1 front up
# 2 permanently open
# i the array of pictures used for this game
# (varies with each reshufflement)
# l the current level (translates into N)
# h which pictures have been "hit", ie selected by the player
# r the refresh rate
#
# the application needs to
# * reshuffle --> generate new i
# * find matching --> check/modify status
# * change level --> keep track of l and reshuffle
# * show help text
# * change refresh rate
# * keep track of best player
#
# turn logging on
#web::logfilter add memory.-debug
#web::logdest add memory.-debug file [file join / tmp websh memory.log]
# config: map level number to X-Y dimensions
set _levels(1) [list 1 2]
set _levels(2) [list 1 4]
set _levels(3) [list 2 5]
set _levels(4) [list 3 6]
set _levels(5) [list 4 7]
set _levels(6) [list 5 8]
set _levels(7) [list 6 9]
# setup file context
web::filecontext mctx -path /tmp/websh/%s.ctx
if {![file exists /tmp/websh/memory.ctx]} {
catch {
# make sure context directory exists
# (note: in a production environment you make sure that this exists
# at install time. You don't want to create that directory with every request)
file mkdir /tmp/websh
# hack ot make the filecontext work
close [open /tmp/websh/memory.ctx w]
}
}
# formatLink -- helper function to generate hrefs
proc formatLink {url {show ""}} {
if {$show == ""} { set show $url }
return "<a href=\"$url\">$show</a>"
}
# putLink -- helper function to output links
proc putLink {url {show ""}} {
web::put [formatLink $url $show]
}
# putLinkHtmlified -- helper function to output links
proc putLinkHtmlified {url show} {
web::put [formatLink $url [web::htmlify $show]]
}
# commandList -- add "commands" line to HTML page
proc commandList {} {
web::put "<tt>"
putLinkHtmlified [web::cmdurl decrementLevel] "<"
web::put " | "
putLinkHtmlified [web::cmdurl incrementLevel] ">"
web::put " | "
putLinkHtmlified [web::cmdurl reset] "x"
web::put " | "
putLinkHtmlified [web::cmdurl new] new
web::put " | "
putLinkHtmlified [web::cmdurl help] "?"
web::put " | "
putLinkHtmlified [web::cmdurl incrRefreshTime] "+"
web::put " | "
putLinkHtmlified [web::cmdurl decrRefreshTime] "-"
# load hall of fame
mctx::init memory
# get the lowest number of tries for this level from the session context
# why do I use web::cmdurlcfg here instead of web::param ?
# I do not want to bother about the level when I generate a URL
# using web::cmdurl - I keep it in the static parameters (managed
# by web::cmdurlcfg).
set best [mctx::cget hof([web::cmdurlcfg l]) "n/a"]
web::put "&nbsp;&nbsp;(level: [web::cmdurlcfg l], [web::cmdurlcfg c] tries, best: $best)"
web::put "</tt>\n"
web::put "<br>\n"
}
# page -- helper function to produce an HTML page
proc page {title code} {
# HTML header stuff
web::put "
<html>
<head>
<title>$title</title>
</head>
<body bgcolor=\"#ffffff\">
"
web::put "<br>\n"
# depends on the caller
uplevel 1 $code
# add list of commands
web::put "<hr>\n"
commandList
# footer and end-of-HTML
web::put "
<hr><font size=\"-2\"><tt>
[web::config version]</tt></font><br>
</BODY>
</HTML>
"
}
# table -- helper function to output a HTML table
proc table {code} {
web::put {<table border="0" cellspacing="0" cellpadding="0">}
web::put "\n"
uplevel 1 $code
web::put "\n</table>\n"
}
# tablerow --
proc tablerow {code {bgcolor {}}} {
if {[string length $bgcolor] } {
web::put "<tr bgcolor=\"$bgcolor\">\n"
} else {
web::put "<tr>\n"
}
uplevel 1 $code
web::put "\n</tr>\n"
}
# tablecell --
proc tablecell {code} {
web::put "<td>\n"
uplevel 1 $code
web::put "\n</td>\n"
}
# image --
proc image {gif} {
set res "<img src=\"/websh/images/memory/$gif\" width=\"50\" height=\"50\" vspace=\"0\" hspace=\"0\""
append res "border=\"1\" ALIGN=\"middle\">"
return $res
}
# validateImg -- check current game status
proc validateImg {vImg vStatus} {
global _levels
upvar $vImg img
upvar $vStatus status
# no images - this calls for a new game. Reshuffle.
if { [string length $img] < 2} {
# reset try counter
web::cmdurlcfg -set c 0
# get current level
set tmp $_levels([web::cmdurlcfg l])
# reshuffle (number of images depends on level)
set numImg [expr ([lindex $tmp 0] * [lindex $tmp 1]) / 2]
for {set i 0} {$i < $numImg} {incr i} {
set timg [format %2.2d $i]
# for this image, generate two random numbers which will
# determine the position of the image in the game
while {1} {
set r1 [expr rand()]
if { ![info exists shuffle($r1)] } { break }
}
while {1} {
set r2 [expr rand()]
if { ![info exists shuffle($r2)] } { break }
}
set shuffle($r1) $timg
set shuffle($r2) $timg
}
# compile string which describes game outline
set img ""
foreach tmp [array names shuffle] {
append img $shuffle($tmp)
}
# and set status of every image to "closed"
set status [string repeat "0" [expr {$numImg * 2}]]
}
}
# listOpen -- helper function to list currently open pictures
proc listOpen {vStatus {val 1}} {
upvar $vStatus status
set i 0
set res ""
foreach tmp [split $status ""] {
if { $tmp == $val } {lappend res $i}
incr i
}
return $res
}
# countOpen -- helper to count all pictures that have a given status
proc countOpen {vStatus {val 1}} {
upvar $vStatus status
set res [listOpen status $val]
return [llength $res]
}
# doMatch -- helper to decide if two selected images match
proc doMatch {vImg vOpens} {
upvar $vImg img
upvar $vOpens opens
set img1 [getImageFromArray img [lindex $opens 0]]
set img2 [getImageFromArray img [lindex $opens 1]]
if {[string equal $img1 $img2]} {
set res [list 1]
lappend res [lindex $opens 0]
lappend res [lindex $opens 1]
} else {
set res [list 0]
lappend res [lindex $opens 0]
lappend res [lindex $opens 1]
}
return $res
}
# getImageFromArray -- helper to extract two letters from string
proc getImageFromArray {vImg pos} {
upvar $vImg img
return [string range $img [expr {$pos * 2}] [expr {$pos * 2 + 1}]]
}
# getStat -- helper to pick status for a given picture
proc getStat {vStatus pos} {
upvar $vStatus status
return [string index $status $pos]
}
# setStat -- set status
proc setStat {vStatus pos {new 0}} {
upvar $vStatus status
set res [string range $status 0 [expr $pos - 1]]
set res $res$new
set res $res[string range \
$status [expr {$pos + 1}] [string length $status]]
set status $res
}
# toggleStat -- toggle status: switch 0->1 or 1->0, but keep 2 at 2
proc toggleStat {vStatus pos} {
upvar $vStatus status
set cur [getStat status $pos]
if {$cur == 0} {
setStat status $pos 1
} elseif { $cur == 2 } {
setStat status $pos 2
} else {
setStat status $pos 0
}
}
# findMatching -- see if the user did find two matching images
proc findMatching {vImg vStatus} {
upvar $vImg img
upvar $vStatus status
# in case only one is open, we prevent closing it again
set tmp [listOpen status 1]
set onlyone -1
if { [llength $tmp] == 1 } {
set onlyone [lindex $tmp 0]
}
# which ones are selected ?
set hitlst [web::param h]
foreach tmp $hitlst {
# if it is not the single one that is already open, flip it
if {$tmp != $onlyone} {
toggleStat status $tmp
}
}
# now, how many are open, really ?
set opens [listOpen status]
set Nopen [llength $opens]
# more than two open ? (no tricks !)
if { $Nopen > 2 } {
foreach tmp $opens {
setStat status $tmp 0
}
} elseif { $Nopen == 2 } {
# get current try counter (or 0 if not set) ...
set tmp [web::cmdurlcfg c 0]
# ... and increment it and store it back as static parameter
web::cmdurlcfg -set c [incr tmp]
# do the two selected pictures match ?
set tmp [doMatch img opens]
if { [lindex $tmp 0] == 1 } {
# yes, open permanently
setStat status [lindex $tmp 1] 2
setStat status [lindex $tmp 2] 2
} else {
# no. use the refresh feature
set opens [listOpen status]
# add img and status as static parameters
# (ensure that we have status and img in the URL)
web::cmdurlcfg -set s $status
web::cmdurlcfg -set i $img
# for refresh: simulate clicks on the two open pictures
# that will close them
set tmp [web::cmdurl "" [list h [lindex $opens 0] h [lindex $opens 1]]]
# add the HTTP "refresh" header, using the parameter r for the
# refresh time (using 2 sec as default)
web::response -set Refresh "[web::cmdurlcfg r 2];URL=$tmp"
}
}
# add img and status as static parameters
# (ensure that we have status and img in the URL)
web::cmdurlcfg -set s $status
web::cmdurlcfg -set i $img
}
# display table with memory
proc showMemory {} {
global _levels
global _cache
# get current status from URL
set status [web::param s]
# get current game outline from URL
set img [web::param i]
# asses status of game
validateImg img status
# do we have any matching images ?
findMatching img status
# no more closed ? --> game over --> perhaps we need to update hof
if { [countOpen status 0] == 0} {
mctx::init memory
set best [mctx::cget hof([web::cmdurlcfg l]) -1]
if { ($best == -1) ||
([web::cmdurlcfg c] < $best) } {
mctx::cset hof([web::cmdurlcfg l]) [web::cmdurlcfg c]
mctx::commit
}
}
# get X-Y dimension for game outline from level
set tmp $_levels([web::cmdurlcfg l 5])
set numX [lindex $tmp 0]
set numY [lindex $tmp 1]
# output HTML page
page "memory game" {
# output HTML table
table {
# table rows
for {set i 0} {$i < $numX} {incr i} {
tablerow {
for {set j 0} {$j < $numY} {incr j} {
set tmpCnt [expr {$i * $numY + $j}]
set timg [getImageFromArray img $tmpCnt]
set curImgStat [getStat status $tmpCnt]
# table cells
tablecell {
if {$curImgStat == 0} {
# show backside
#
# actually, it is a link back to the CGI app,
# recursion of some sort.
#
# from parameter h, showMemory will know which
# picture the player did select
putLink [web::cmdurl "" h $tmpCnt] \
[image back.gif]
} elseif {$curImgStat == 2} {
# two matching found - no link any more, just the image
web::put [image $timg.jpg]
} else {
# show front side
putLink [web::cmdurl "" h $tmpCnt] \
[image $timg.jpg]
}
}
}
}
}
}
}
}
# web::command help -- display help text
web::command help {
page "memory game - help text" {
web::put "<tt>"
web::put "Memory -- find the matching images."
web::put "<p>"
web::put "You can see the hidden image by clicking on its back side. "
web::put "When you have found two matching images, they will remain open "
web::put "from then on. If two images do not match, they will be "
web::put "closed again."
web::put "<p>"
web::put "If the images close again too quickly on your system, "
web::put "you can make the images stay open longer with the "
web::put "&quot;+&quot; command (&quot;-&quot; to close them "
web::put "more quickly)."
web::put "<p>"
web::put "&quot;new&quot; shuffels the images again. "
web::put "&quot;&times;&quot; restarts the game from the beginning."
web::put "</tt><p>"
}
}
# web::command decrementLevel -- reduce level and show game
web::command decrementLevel {
# I do not want to have to bother about the level when I generate
# URLs using web::cmdurl. So, I put level to the static parameters
# and let web::dispatch track it.
# That's why web::cmdurlcfg is used here, instead of web::param.
set level [web::cmdurlcfg l 5]
if {$level > 1} {incr level -1}
web::cmdurlcfg -set l $level
# changing the level implies resetting the game
web::param -set i ""
showMemory
}
# web::command incrementLevel -- increase level and show game
web::command incrementLevel {
set level [web::cmdurlcfg l 5]
if {$level < 7} {incr level}
web::cmdurlcfg -set l $level
# changing the level implies resetting the game
web::param -set i ""
showMemory
}
# web::command incrRefreshTime -- increase refresh time
web::command incrRefreshTime {
set r [web::cmdurlcfg r 2]
if {$r < 30} {incr r 2}
web::cmdurlcfg -set r $r
showMemory
}
# web::command decrRefreshTime -- decrease refresh time
web::command decrRefreshTime {
set r [web::cmdurlcfg r 2]
if {$r > 2} {incr r -2}
web::cmdurlcfg -set r $r
showMemory
}
# web::command new -- new game on the same level (reshuffle)
web::command new {
web::param -set i ""
showMemory
}
# web::command reset -- back to the defaults
web::command reset {
web::param -set i ""
web::cmdurlcfg -set l 5
showMemory
}
# web::command default -- if nothing is specified, use this one
web::command default {
showMemory
}
# web::dispatch -- decide which command to call
#
# here, we use the tracking feature of dispatch. Whenever dispatch
# finds a parameter from the -track list, it copies it over to the
# static parameters
#
# also, we use -hook to execute code just before web::dispatch will
# call the web::command command. Here, we set the default level to 5
# if it is not yet known.
web::dispatch -track [list l c r] -hook {web::cmdurlcfg -set l [web::cmdurlcfg l 5]}
# cleanup context after the request (prevent session crosstalk)
mctx::delete