--- title: "Hangcat" id: 53798 type: "computer_media" slug: "hangcat" url: "http://localhost/computer_media/hangcat/" markdown_url: "http://localhost/computer_media/hangcat.md" published_at: "2024-05-13T18:56:52+00:00" modified_at: "2026-03-30T21:42:56+00:00" author: "David Anderson" featured_image: url: "http://localhost/wp-content/uploads/2019/02/20230809-041332.jpg" excerpt: "A two-player word-guessing game with a clever UDG-based animated title that copies ROM font data character by character into custom graphic slots." category: - name: "Archived Media" slug: "archived-media" taxonomy: "category" url: "http://localhost/category/archived-media/" post_tag: - name: "Downloadable" slug: "downloadable" taxonomy: "post_tag" url: "http://localhost/tag/downloadable/" - name: "TS 2068" slug: "ts2068" taxonomy: "post_tag" url: "http://localhost/tag/ts2068/" model: - name: "Timex/Sinclair 2068" slug: "ts-2068" taxonomy: "model" url: "http://localhost/model/ts-2068/" genre: - name: "Game" slug: "game" taxonomy: "genre" url: "http://localhost/type/game/" media_contents: - id: 53877 title: "SINCUS Exchange Tape 101 – Entertainment" type: "computer_media" url: "http://localhost/computer_media/sincus-exchange-tape-101-entertainment/" media_type: "Program" images: - url: "http://localhost/wp-content/uploads/2024/05/hangcat.png" media_type_tags: "Game" --- # Hangcat Hangcat is a two-player word-guessing game in the Hangman tradition, where one player enters a secret word and the second player attempts to guess it letter by letter within nine lives. The program uses a dash-filled string `g$` as a mask that reveals correctly guessed letters in place, and tracks used letters in the string `u$` for display. A subroutine at line 2000 renders an animated title by copying character ROM data into two UDG slots (`\a` and `\b`) on a per-character basis, effectively scrolling custom-shaped glyphs across two display rows. BEEP sequences signal both incorrect guesses and a winning fanfare, and FLASH 1 highlights the win/loss message. *** ## Program Analysis ### Program Structure The program is organized into three logical phases: setup (lines 5–95), the main game loop (lines 100–330), and a title-screen subroutine (lines 2000–2030). A `SAVE` statement at line 9999 acts as a standalone utility line for archiving the program with auto-run at line 1 (which falls through to line 5). 1. **Initialization (lines 10–95):** Sets screen colors, calls the title subroutine, prompts Player 1 for a secret word, and builds the dash-mask string `g$`. 2. **Game loop (lines 100–330):** Displays the current mask, lives remaining, used letters, and solicits a letter guess each iteration. 3. **Title subroutine (lines 2000–2030):** Renders the title string using dynamically loaded UDGs. ### Word Masking and Guess Matching The secret word is stored in `a$`. The mask string `g$` is initialized to a string of dashes equal in length to `a$` (lines 60–90). When the player guesses a letter `x$`, lines 160–200 iterate over each character of `a$`; a match sets the flag `f=1` and writes `x$` directly into the corresponding position of `g$` via `LET g$(z)=x$` (line 190), exploiting Sinclair BASIC’s in-place string character assignment. Win detection at line 260 is a simple string equality test: `g$=a$`. When all dashes are replaced by correct letters, the condition becomes true. The lives counter `t` starts at 9 and decrements by 1 for each incorrect guess (line 250). ### UDG-Based Title Rendering (Subroutine 2000) The most technically distinctive feature is the animated title. Line 2020 loops over every character in the title string `m$`. For each character, it calculates the ROM font address: `c = 15616 + (8 * (CODE m$(a) - 32))`. This is the standard TS2068/Spectrum formula for locating the 8-byte bitmap of a character in the ROM font (font starts at address 15616). It then copies those 8 bytes into *two* UDG slots simultaneously — UDG `\a` and UDG `\b` — by writing each byte to both `USR "a" + (b*2)` and `USR "a" + (b*2) + 1`. This doubles the width of each glyph horizontally, producing a wider, blocky title character. Each character is then printed at row `y` and row `y+1` using `INVERSE 1`, giving a two-row tall inverse-video title. ### Key BASIC Idioms and Techniques - **In-place string assignment:**`LET g$(z)=x$` replaces a single character within a string, a Spectrum/TS2068 BASIC feature not available on all platforms. - **Used-letters tracking:**`u$` starts as a single space and grows by concatenation each turn (line 145). The condition `IF u$<>" "` at line 125 suppresses display until at least one guess has been made. - **Centered text:**`TAB 15-(LEN g$/2)` (line 220) and `AT y, (32-LEN m$)/2` (line 2010) perform runtime centering calculations. - **BEEP fanfares:** A losing sequence uses three discrete `BEEP` calls (line 270); a winning sequence uses a `FOR` loop ascending in pitch (line 280). - **Input validation:** Both the word input (line 55) and the letter input (line 142) reject empty strings with a redirect back to the `INPUT` line. ### Variable Summary | Variable | Role | | --- | --- | | `a$` | Secret word entered by Player 1 | | `g$` | Current guess mask (dashes replaced by correct letters) | | `u$` | Accumulated string of letters guessed so far | | `x$` | Current letter guess | | `t` | Lives remaining (starts at 9) | | `f` | Flag: 1 if last guess was correct, 0 otherwise | | `m$` | Title string used in subroutine 2000 | | `c` | ROM font address for current title character | | `d` | UDG destination address during title rendering | ### Bugs and Anomalies - **Used-letters string grows unbounded:**`u$` appends every guess including repeats, so a player who guesses the same letter twice will see it listed twice. There is no duplicate-guess check. - **Line 290 layout:** The comma after `a$` before `TAB` (`PRINT "..."-,;TAB...`) produces a comma-separator print, which may cause unexpected column advancement depending on the current print position; the semicolon modifier on `,;` is redundant but harmless. - **Play-again loop:** Lines 320–330 poll `INKEY$` in a tight loop but only check for `"y"`; pressing `"n"` does nothing and the loop continues indefinitely. The program can only be exited by pressing `BREAK` or entering `"y"`. - **UDG byte stride:** The title subroutine writes each font byte to `USR "a" + (b*2)`, stepping by 2 rather than 1 through the UDG area. This means only bytes at even offsets from `USR "a"` are written, which spans into UDG `\b` territory — intentional, as it populates both UDGs, but the approach is unusual and tightly coupled to UDG memory layout. ## Source Code ``` 5 REM hangcat Version 3.0 10 BORDER 1: PAPER 1: INK 7: CLS 15 GO SUB 2000 20 PRINT AT 16,4;"Player 1 - Type in a word." 25 PRINT AT 18,5;"(Player 2 - DON'T LOOK!)" 50 INPUT a$ 55 IF a$="" THEN GO TO 50 60 LET g$="-" 70 FOR z=1 TO LEN a$-1 80 LET g$=g$+"-" 90 NEXT z 95 LET u$=" " 100 LET t=9 110 CLS 111 PRINT : PRINT : PRINT AT 4,(32-LEN g$-1)/2;g$: PRINT : PRINT : PRINT 115 IF t<2 THEN PRINT " You have 1 life.": GO TO 123 120 PRINT " You have ";t;" lives." 123 PRINT : PRINT 125 IF u$<>" " THEN PRINT "Letters used=";u$ 127 PRINT : PRINT 130 PRINT " GUESS A LETTER OF THE WORD." 140 INPUT x$ 142 IF x$="" THEN GO TO 140 143 CLS 145 LET u$=u$+x$ 150 LET f=0 160 FOR z=1 TO LEN a$ 170 IF x$<>a$(z) THEN GO TO 200 180 LET f=1 190 LET g$(z)=x$ 200 NEXT z 210 PRINT : PRINT : PRINT : PRINT 220 PRINT TAB 15-(LEN g$/2);g$ 225 PRINT : PRINT : PRINT 230 IF f=0 THEN PRINT TAB 11;"INCORRECT" 240 IF f=1 THEN PRINT TAB 12;"CORRECT" 245 PRINT : PRINT 250 IF f=0 THEN LET t=t-1 260 IF t>0 AND g$<>a$ THEN GO TO 115 270 IF g$<>a$ THEN PRINT " "; FLASH 1;"YOU'RE A GONER.": BEEP 0.7,1: BEEP 0.5,1: BEEP 1,6 280 IF g$=a$ THEN PRINT " "; FLASH 1;"CONGRATULATIONS!": FOR x=1 TO 30: BEEP .01,x: NEXT x 285 PRINT : PRINT 290 PRINT " The correct word was-",;TAB 15-(LEN g$/2);a$ 300 PRINT 310 PRINT " Want to play again? (y/n)" 320 IF INKEY$="y" THEN RUN 330 GO TO 320 2000 REM title 2010 LET m$=" H A N G C A T ": LET y=8: LET x=((32-LEN m$)/2) 2020 FOR a=1 TO LEN m$: LET c=15616+(8*(CODE m$(a)-32)): FOR b=0 TO 7: LET d=(USR "a"+(b*2)): POKE d,PEEK (c+b): POKE (d+1),PEEK (c+b): NEXT b: PRINT AT y,x; INVERSE 1;"\a";AT y+1,x;"\b": LET x=x+1: NEXT a 2030 RETURN 9999 SAVE "hangcat" LINE 1: BEEP 1,33 ```