{{ Hackish Hangman -- telnet-controlled game Version 0.1.4 by Jacob Rose (jacob@thinkgeek.com) ABOUT Telnet to port 42 of your ybox with this widget loaded to join the game. The game is simple -- a word from the Jargon file is selected at random by the computer and blanks appear on-screen for each letter. As you type letters on your telnet terminal, any matching letters in the word are lit up. Any letters that don't match cause the scaffold to be built and your man to be hanged from it. Eek! THANK YOU! Thanks to deepdarc for the ybox2 class, AdaFruit for kit instantiation, and Raphael Finkel, Mark Crispin, Guy Steele, Richard Stallman, Eric Raymond, and all the other contributors to the File for their font of wisdom and humor. LICENSE Copyright (c) 2008, Jacob Rose All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the ThinkGeek nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. CHANGELOG 0.1.0 (2008-10-28): * First post to ladyada.net 0.1.1 (2008-10-28): * Tweak to clean things up for line-based telnet clients * Improved telnet-facing messages 0.1.2 (2008-10-29): * Changed button functionality from reboot to hangup; a timeout would probably be a better solution to the problem of sessions left open. * Added last word notice at login 0.1.3 (2008-10-30): * Fixed last-guess bug in duplicate guess detection * Prevented the same word from coming up twice * Fixed range boundary bug in word selection 0.1.4 (2008-11-18): * Added timeout after TIMEOUT cycles with no input }} CON ' Unless the clock is set just-so, TV output won't occur at all _CLKMODE = XTAL1 + PLL16X _XINFREQ = 5_000_000 WORDMAX = 20 ' Maximum word size GAMEPORT = 42 ' This game is less fun on other ports; try to stick with port 42 BODYPARTS = 6 TIMEOUT = 60000 ' After this many loops without a keypress, give up and hang up on them OBJ subsys : "subsys" control : "api_telnet_serial" term : "TV_Text" settings : "settings" VAR byte tv_mode byte player ' player count byte i ' generic loop index byte temp ' generic temp variable ' Player values byte p_input ' last keystroke read from player byte ListenerStack[ 900 ] ' Stack space for listener cog byte network_address[ 4 ] ' IP address long RandomSeed ' Random number seed long wordbanklength ' length of the entire wordbank long jargonoffset ' the position within the wordbank where our selected word starts long jargonlength ' length of the selected word long theend ' end of the selected word long lastoffset ' the start of the previous word long lastlength ' the length of the previous word long lastwon ' whether the last player won (0), lost (1), chickened out (2) or was hung up on (3) long pos ' generic position index byte wrongguesses ' how close are we to being hanged? byte right[ WORDMAX + 1 ] ' The answer so far byte guesses[ 26 ] ' how many of the 26 letters we've used! byte guesscount ' how many total guesses? this is the index for guesses long idle_for ' count of the number of cycles the user has been idle for (up to TIMEOUT before we hang up) byte gameover ' flag to force the game to stop DAT game_name byte "Hackish Hangman!", 0 ' These next two go together... wordbank byte "abend ad-hockery adger adware ambimouseterous amper annoybot anti-idiotarianism armm asbestos_longjohns astroturfing attoparsec autobogotiphobia autoconfiscate automagically backreference backronym backward_combatability bagbiter bagbiting baggy_pantsing balloonian_variable bamf barfmail barfulation barfulous batbelt bboard bcpl bdfl big-endian bignum bikeshedding bitblt bixie blargh bletcherous blinkenlights blit blitter blog blogosphere blogrolling blurgle bofh bogo-sort bogue_out borken boxology bozotic braino brainwidth breedle bricktext broket bsod buaf buag bucky_bits bullschildt bytesexual cancelbot candygrammar careware cd_tilde chainik chanop charityware chawmp chickenboner chiclet_keyboard choad clustergeeking codewalker cokebottle computron copybroke copycenter copyparty copywronged cow_orker crapplet creeping_featurism creeping_featuritis crlf crossload crudware cruftsmanship cryppie cthulhic ctss cuspy cybercrud cypherpunk dahmum de-rezz deadbeef deflicted dehose deletia demoeffect demogroup demoparty demoscene dentro depeditate derf despew dickless_workstation disemvowel dispress distro disusered dogcow dogfood dogpile dogwash double_bucky doubled_sig drecnet dumbass_attack dwim dynner ebcdic egosurf eurodemo exch fandom faql farkled featurectomy feep feeper feeping_creature feeping_creaturism feetch_feetch field_servoid filk finn firebottle firehose_syndrome fisking fitnr fixme flarp flypage fnord foaf fontology fred frednet frink friode fritterware frob frobnicate frobnitz frotz frotzed frowney fscking fubar functino furrfu garply gawble gcos gecos gedanken geef geekasm gensym gilley gips giyf glark glass_tty glassfet glork gnubie gnumacs go_flatline gonkulator google google_juice gorets gosmacs greenbar grilf gritch gronk gronk_out gronked gubbish guiltware gumby gunch gweep hakmem hakspek handwave hardcoded hardwarily heatseeker heisenbug hexit hhok hhos high_moby hollised horked hungus ianal ifdef_out iirc initgame installfest intercal intertwingled iwbni iyfeg jedr jibble joe_code joe-job jupiter kgbvax kibo kiboze kibozo kilogoogle klone knurd kremvax kyrka lart lenna lerp lexiphage linearithmic little-endian livelock lossage lossy luser macdink machoflops maggotbox mainsleaze malware mandelbug manularity marketroid meatspace meatware meeces megapenny memetics menuitis mftl microfortnight microserf middle-endian misbug miswart mobo moby mojibake monty moria motas motos motss mouso muddie mudhead muggle mumblage munge nadger narg nastygram neep-neep netburp netdead nethack netlag netnews netsplit newgroup_wars newsfroup nugry ooblick operator_headspace param parm pebkac perf pessimizing_compiler pffft phreaker playte plokta plugh postcardware precedence_lossage pred prepend prestidigitization prettyprint progasm proggy proglet pseudoprime pseudosuit psychedelicware psyton pumpking quadruple_bucky quantum_bogodynamics quux rasterbation rathole ravs rc_file rectangle_slinger retcon reti retrocomputing robocanceller rootkit rtbm rtfaq rtfb rtfs sagan salescritter sandbender scanno schroedinbug scnr screaming_tty screwage scrool scrozzle scruffies seggie segv shar_file sharchive shelfware shitogram shovelware sig_block sig_quote sig_virus sigmonster sitename skrog slashdot_effect slopsucker smoot smop sneakernet snippage softcopy softwarily spacewar spamhaus spamvertize spod sporgery spungle spyware squirrelcide steved stfw strided stubroutine studlycaps superloser superprogrammer sys-frog sysape tanstaafl tardegy tayste teco teergrube teledildonics teraflop_club terminak thinko timesharing tinc tinlc tmrc tmtowtdi toeprint toolchain toolsmith toor treeware trit troff troughie tunafish turist twenex tyop ubergeek unswizzle upthread uptime userland utsl uuoc vadding vannevar vaston vaxocentrism vdiff veeblefester velveeta verbage vgrep wallhack wango warlording webify wedgitude weeble whitelist whizzy wibble wibni windoid winkey winnage winnitude wirehead wirewater wugga_wugga wumpus wysiayg xoff xyzzy yaba yafiygi yaun yhbt ykybhtlw yoyo_mode zbeba zipperhead zorkmid " wordbank_terminator byte 0 palette 'swiped from twitterwidget byte $07, $B2 '0 white / dark blue byte $9E, $B2 '1 yellow / black byte $3D, $B2 '2 yellow / brown byte $04, $07 '3 grey / white byte $3D, $3B '4 cyan / dark cyan byte $6B, $6E '5 green / gray-green byte $BB, $CE '6 black, white byte $9E, $B2 '7 red, black PUB HackishHangman Init cognew( Listener, @ListenerStack ) repeat if ina[subsys#BTTNPin] if control.isconnected control.close gameover := 1 lastwon := 3 lastoffset := jargonoffset lastlength := jargonlength PUB Listener repeat ' Wait for someone to connect repeat while control.listen(42) < 0 RandomSeed++ Pause ?RandomSeed ' Wait for connection to complete repeat while NOT control.waitConnectTimeout( 100 ) ResetGame player++ control.str( string( "Hackish Hangman! Welcome, player " ) ) control.dec( player ) control.str( string( "!", 13, 10 ) ) if lastwon => 0 case lastwon 0: control.str( string( "The last player won by guessing the word '" ) ) 1: control.str( string( "The last player was hanged for not guessing the word '" ) ) 2: control.str( string( "The last player chickened out when contronted with the word '" ) ) 3: control.str( string( "The gallowsmaster pulled the switch before the last player could guess '" ) ) 4: control.str( string( "The last player was hanged for loitering before guessing the word '" ) ) repeat pos from lastoffset to lastoffset + lastlength - 1 control.tx( wordbank[ pos ] ) control.str( string( "'", 13, 10 ) ) control.str( string( "Type period ('.') to disconnect or any letter to guess.", 13, 10 ) ) TellEmAll p_input := "!" repeat while p_input <> "." and control.isconnected and NOT gameover p_input := control.rxcheck ' Used this to find out that NULL is 251: control.dec( p_input ) if p_input <> 251 idle_for := 0 case p_input "." : other : Guess( p_input ) else idle_for++ if idle_for > TIMEOUT control.str( string( "'Round here, loitering's a hangin' crime.", 13, 10 ) ) control.close gameover := 1 lastwon := 4 lastoffset := jargonoffset lastlength := jargonlength else if idle_for == TIMEOUT / 2 control.str( string( "I can't wait around all day for you to guess.", 13, 10 ) ) if NOT control.isconnected AND idle_for =< TIMEOUT ' user hanged up lastwon := 2 ' chickeennnn! bwak bwak! lastoffset := jargonoffset lastlength := jargonlength control.close PRI Guess( letter ) | empty, matches ' matches == number of matching letters in the current word for a given guess ' empty == number of letters missing from the answer control.str( string( 13, 10 ) ) ' validate input if ( letter => "A" ) AND ( letter =< "Z" ) letter += ( "a" - "A" ) if ( letter < "a" ) OR ( letter > "z" ) if ( letter > 26 ) control.str( string("I'm going to pretend you didn't just try to waste a guess on a non-alpha character", 13, 10 ) ) return repeat temp from 0 to guesscount if letter == guesses[ temp ] control.str( string("You already guessed that one -- try something new!", 13, 10 ) ) return repeat temp from "a" to letter ?RandomSeed ' Keep randomizing! ' valid input now! record this guess Showletter( letter ) guesscount++ guesses[ guesscount ] := letter matches := 0 repeat pos from 0 to jargonlength - 1 if wordbank[ jargonoffset + pos ] == letter matches ++ right[ pos ] := letter LightUp( pos ) empty := 0 repeat pos from 0 to jargonlength - 1 if right[ pos ] == 0 empty ++ if empty == 0 control.str( string("You win!", 13, 10 ) ) TellEmAll gameover := 1 lastoffset := jargonoffset lastlength := jargonlength lastwon := 0 return if matches == 0 ?RandomSeed control.str( string("No '") ) control.tx( letter ) control.str( string("'s", 13, 10 ) ) HangEmHigh else control.str( string("Got one! There " ) ) if matches > 1 control.str( string("are ") ) else control.str( string("is ") ) control.dec( matches ) control.str( string(" '") ) control.tx( letter ) control.tx( "'" ) if matches > 1 control.tx( "s" ) control.str( string( 13, 10 ) ) TellEmAll return PRI TellEmOne( loc ) | chartoshow ' show the specified character case right[ loc ] "-" : chartoshow := "-" "_" : chartoshow := " " other: if right[ loc ] == wordbank[ jargonoffset + loc ] chartoshow := right[ loc ] else chartoshow := "_" control.tx( chartoshow ) control.tx(" ") PRI TellEmAll ' show the board repeat pos from 0 to jargonlength - 1 TellEmOne( pos ) control.str( string( 13, 10 ) ) PRI LightUp( loc ) | chartoshow SelectLetter( loc ) ' Draw the letter at position loc chartoshow := wordbank[ jargonoffset + loc ] if chartoshow == "_" chartoshow := " " term.out( chartoshow ) PRI SelectLetter( loc ) ' Position cursor at row, col position of letter in position pos term.out( $0A ) term.out( ( loc * 2 ) + 20 - jargonlength ) ' don't need to divide jargonlength by two cuz we're double spacing term.out( $0B ) term.out( 11 ) PRI DrawABlank( loc ) | chartoshow case wordbank[ jargonoffset + loc ] "-": chartoshow := "-" "_": chartoshow := " " other: if right[ loc ] chartoshow := right[ loc ] else chartoshow := "_" term.out( chartoshow ) PRI HangEmHigh ' I hope they don't hang you, precious, by that sweet neck. wrongguesses ++ case wrongguesses 1: Drawscaffold ' Thanks, Dave Barry! control.str( string("Something is not boding the way it should. It could be boding better.", 13, 10 ) ) 2: Drawhead control.str( string("Oh no, it's up to my toe!", 13, 10 ) ) 3: Drawbody control.str( string("Oh gee, it's up to my knee.", 13, 10 ) ) 4: Drawlegs control.str( string("Oh fiddle, it's up to my middle.", 13, 10 ) ) 5: Drawarms ' Thank you, Shel Silverstein! control.str( string("Oh heck, it's up to my neck.", 13, 10 ) ) 6: Drawdrop if wrongguesses => BODYPARTS control.str( string("You've been hanged! The word was: ") ) Reveal lastwon := 1 ' hanged! lastoffset := jargonoffset lastlength := jargonlength gameover := 1 PRI Drawscaffold Drawtext( 18, 3, string( "+====+") ) Drawtext( 21, 4, string( "\ |") ) Drawtext( 22, 5, string( "\|") ) Drawtext( 23, 6, string( "|") ) Drawtext( 13, 7, string("__________|") ) repeat temp from 8 to 10 Drawtext( 13, temp, string("X X X X") ) PRI Drawhead Drawtext( 18, 4, string( "O" ) ) PRI Drawbody Drawtext( 18, 5, string( "|" ) ) PRI Drawlegs Drawtext( 17, 6, string( "| |" ) ) PRI Drawarms Drawtext( 16, 5, string( "--|--" ) ) PRI Drawdrop ' stage 1: trapdoor Drawtext( 16, 7, string( " " ) ) ' hole in platform Drawtext( 20, 8, string( "/" ) ) ' trapdoor open repeat temp from 0 to 3 Drawtext( 16, 5, string( "`-|-'" ) ) Pause Pause Pause Drawtext( 16, 5, string( ",-|-." ) ) Pause Pause Pause ' stage 2: some rope Drawtext( 18, 4, string( "|" ) ) Drawtext( 16, 5, string( " O " ) ) Drawtext( 16, 6, string( "`-|-'" ) ) Drawtext( 17, 7, string( "/ \" ) ) Pause Pause Pause ' stage 3: more rope Drawtext( 18, 5, string( "|" ) ) Drawtext( 16, 6, string( " O ," ) ) Drawtext( 16, 7, string( ",-|' " ) ) Drawtext( 17, 8, string( "| \/" ) ) Pause Pause Pause ' stage 4: the end of your rope Drawtext( 18, 6, string( "| " ) ) Drawtext( 16, 7, string( " O " ) ) Drawtext( 16, 8, string( " /|\/" ) ) Drawtext( 17, 9, string( "| |" ) ) PRI Drawtext( x, y, text ) term.out( $0A ) term.out( x ) term.out( $0B ) term.out( y ) term.str( text ) PRI Drawchar( x, y, char ) term.out( $0A ) term.out( x ) term.out( $0B ) term.out( y ) term.out( char ) PRI ShowLetter( letter ) | x, y letter -= "a" case ( letter / 7 ) 0 : x := letter // 7 y := 4 1 : x := letter // 7 y := 7 2 : x := 33 + ( letter // 7 ) y := 4 3 : x := 33 + ( letter // 7 ) y := 7 letter += "a" repeat i from 1 to 10 Drawchar( x, y + orbit( i ), letter ) Drawchar( x, y + orbit( i - 1 ), " " ) pause pause pause PRI orbit( frame ) case frame // 4 0: return 0 1: return 1 2: return 0 3: return -1 PRI DrawAllBlanks repeat pos from 0 to jargonlength - 1 SelectLetter( pos ) DrawABlank( pos ) control.str( string( 10, 13 ) ) PRI Reveal | chartoshow repeat pos from 0 to jargonlength - 1 SelectLetter( pos ) LightUp( pos ) case wordbank[ jargonoffset + pos ] "_" : chartoshow := " " other: chartoshow := wordbank[ jargonoffset + pos ] control.tx( chartoshow ) control.tx( " " ) control.str( string( 13, 10 ) ) PRI SelectWord ?RandomSeed ' Search for the next space jargonoffset += RandomSeed jargonoffset //= wordbanklength ||jargonoffset repeat while ( ( jargonoffset < wordbanklength - 1 ) AND ( wordbank[ jargonoffset ] <> " " ) ) jargonoffset ++ 'control.str( string("Random index at ") ) 'control.dec( jargonoffset ) 'control.str( string(" of ") ) 'control.dec( wordbanklength ) 'control.str( string( " total wordbank letters", 13, 10 ) ) ' advance to first character of the word we found if ( jargonoffset => wordbanklength - 1 ) jargonoffset := 0 else jargonoffset ++ 'control.str( string("Start of new word at ") ) 'control.dec( jargonoffset ) 'control.str( string( 13, 10 ) ) ' Find the length of the word we landed on theend := jargonoffset repeat while ( wordbank[ theend ] <> " " ) theend ++ if ( theend > wordbanklength ) theend := 0 'control.str( string("End of new word at ") ) 'control.dec( theend ) 'control.str( string( 13, 10 ) ) jargonlength := theend - jargonoffset ' we have to clear the answer space, but leave any characters that won't be updated ' so that when the rest are populated, the strings will match repeat temp from 0 to WORDMAX case wordbank[ jargonoffset + temp ] "_" : right[ temp ] := "_" "-" : right[ temp ] := "-" other: right[ temp ] := 0 PRI Threebeep subsys.chirpHappy Pause return PRI ResetGame ' reset idle time idle_for := 0 ' reset wrong guess count wrongguesses := 0 ' reset total guess count guesscount := 0 gameover := 0 ' pick a new word SelectWord ' and it better be new! repeat while lastoffset == jargonoffset SelectWord RedrawAll DrawAllBlanks PRI Init subsys.init settings.start 'subsys.StatusLoading ' How long is that word bank anyway? wordbanklength := @wordbank_terminator - @wordbank ' Reset idle conter idle_for := 0 ' Initialize speaker for output dira[subsys#SPKRPin] := 1 ' Initialize button for input dira[subsys#BTTNPin] := 0 'ina[subsys#BTTNPin] := 0 ' and set it low initially ' Initialize terminal display if settings.findKey( settings#MISC_TV_MODE ) tv_mode := settings.getByte( settings#MISC_TV_MODE ) else tv_mode := term#MODE_NTSC term.startWithMode( 12, tv_mode ) ' use pin 12 for output term.setcolors( @palette ) term.str(string($0C,1)) ' choose color 7 term.str( string("Welcome to Hackish Hangman!") ) term.out( 13 ) ' Initialize ethernet term.str( string("Starting network...") ) dira[0] := 1 outa[0] := 0 ' start resetting ethernet device Pause ' allow reset to happen outa[0] := 1 ' stop resetting ethernet device repeat while not \control.start( 1, 2, 3, 4, 6, 7 ) ' magic formula from twitwidget outa[0] := 0 subsys.chirpSad term.str( string( "uh-oh.", 13, "Trying again..." ) ) Pause ' keep trying outa[0] := 1 term.str( string( 13, "Okay!", 13, "Waiting for DHCP" ) ) repeat while not settings.getData( settings#NET_IPv4_ADDR, @network_address, 4 ) term.out(".") Pause ' keep trying RandomSeed += network_address[ 3 ] ' Start random seed at IP address repeat pos from 0 to network_address[ 2 ] ?RandomSeed player := 0 lastwon := -1 term.str( string( 13, "Okay!") ) Threebeep ' Initialize display RedrawAll PRI Pause waitcnt( 3_000_000 + cnt ) PRI RedrawAll ClearScreen ' Write tagline at top of screen term.out( $0A ) term.out( 11 ) term.out( $0B ) term.out( 0 ) term.str( string("Hackish Hangman!") ) term.out( $0A ) term.out( 9 ) term.out( $0B ) term.out( 1 ) term.str( string("from the Jargon File") ) ' Write instructions at the bottom of the screen term.out( $0A ) term.out( 3 ) term.out( $0B ) term.out( 12 ) term.str( string("telnet ") ) repeat i from 0 to 3 if i term.out(".") term.dec( byte[ @network_address ][ i ] ) term.str( string(" 42 to play" ) ) PRI ClearScreen term.out( $00 )