This program calculates and displays biorhythm charts for a given birth date and target month, plotting Physical (23-day), Emotional (28-day), and Intellectual (33-day) cycles on a 32×24 character screen grid. The user enters birth date and desired month, and the program computes the number of elapsed days to determine each cycle’s phase using sine functions scaled to row positions. Input validation checks for impossible month numbers, day values over 31, and target dates before the birth year. The chart uses FAST mode for calculation, then switches to SLOW for display, with optional COPY output to a ZX Printer and navigation keys to step forward by month or restart entirely. A POKE to address 16418 is used to control the printer buffer/display area.
Program Analysis
Program Structure
The program is organized into a clear sequence of phases:
- Data entry (lines 2–67): Prompts for birth date, target month/year, printer choice, and a 4-letter name.
- Leap-year detection (lines 77–165): Computes the number of days remaining in the birth year after the birth date (
DA1). - Elapsed-year accumulation (lines 250–315): Counts days between end of birth year and start of target year (
DA2), correctly inserting 366-day years for leap years. - Days-into-target-month (lines 320–404): Adds the cumulative day-of-year for the target month’s start (
DA3). - Phase calculation (lines 405–424): Computes
DAYT(total elapsed days) and derives display row offsetsFUDGP,FUDGE,FUDGIfor each cycle’s first day in the target month. - Array filling (lines 745–946): Runs in
FASTmode, filling 32-element arraysP(),E(),I()with screen row values (0–20) for each day. - Display (lines 690–727, 952–967): Renders the chart frame, axis labels, month name, and plots
P,E,Icharacters at computed positions. - Navigation (lines 990–1066): Allows advancing to the next month, restarting, or stopping.
Day-Count Arithmetic
The total elapsed days from birth to the start of the target month is built from three components:
DA1: days remaining in the birth year after the birth date (lines 80–135 for non-leap, 155–160 for leap January/February).DA2: sum of days in all complete years between birth year+1 and the target year (lines 260–315), with leap years yielding 366.DA3: cumulative day offset to the start of the target month within the target year (lines 325–380), with a +1 correction at line 395 if the target year is a leap year and the month is March or later.
The combined total DAYT = DA1 + DA2 + DA3 - 1 (line 405) gives the number of days from the birth date to the first day of the target month.
Biorhythm Phase and Screen Mapping
Lines 422–424 compute the display column offset for day 1 of the target month within each cycle:
FUDGP(Physical, period 23):INT(.5 + 22 - (DAYT/23 - INT(DAYT/23))*23)FUDGE(Emotional, period 28):INT(.5 + 27 - (DAYT/28 - INT(DAYT/28))*28)FUDGI(Intellectual, period 33):INT(.5 + 32 - (DAYT/33 - INT(DAYT/33))*33)
The fractional part of DAYT/period gives the phase offset, which is subtracted from the period length and rounded to find how many days remain in the current cycle at the month boundary. The sine functions in the array-fill loops then use this offset to place the plotted character at the correct screen row.
Array Filling and Sine Scaling
The array-fill loops (e.g., lines 755–805 for Physical) use two nested strategies:
- Forward fill: for day index
P, compute columnX = FUDGP + P; ifX ≤ 31, setP(X+1) = 11 - 5*SIN(P/11.5*PI)(descending half of cycle). - Backward fill: if
X > 31, iterate backwards from the fudge offset to fill the ascending half usingP(XX+1) = 11 + 5*SIN(Q/11.5*PI).
The centre row is 11 (midpoint of the 0–20 usable display range). Amplitude scaling is ±5 rows for Physical, ±7 for Emotional, ±9 for Intellectual, matching the relative importance traditionally assigned to each rhythm.
Notable Techniques
- FAST/SLOW switching:
FAST(line 76, 745) is used during calculation to suppress screen updates and speed up the sine-heavy array fills;SLOW(line 690) is restored for display. - POKE 16418: Used at line 406 (set to 0) and line 968 (set to 12). Address 16418 is the system variable
DF_SZ, the number of lines in the lower display area. Setting it to 0 suppresses the lower screen during calculation; restoring it to 12 after plotting is unusual — the normal value is 2 — and may be an attempt to extend the display or manage COPY output. - COPY for printer output: Line 970 conditionally issues
COPYbased on the user’s initialA$="Y"choice, sending the screen to a ZX Printer. - Looping navigation: Lines 1061–1066 increment
BMN, handle year rollover, and jump back to line 76 (FAST mode re-entry) to recalculate for the next month without re-entering the birth date. - Array variable name collision: The program uses
Pboth as a scalar loop variable (lines 755, 952) and as an arrayP()(line 750). BASIC resolves these as distinct entities, but the practice is potentially confusing.
Bugs and Anomalies
- DA validation is incomplete: The check at line 57 only rejects
DA > 31; days 29–31 are accepted for February, and day 31 is accepted for 30-day months. This can produce slightly incorrect day counts. - BYR=YR check (line 255): The program stops if the birth year equals the target year (line 257), but allows target years only one year ahead of birth without issue. However, the user could enter a target month/year in the birth year but a later month — this would produce incorrect results rather than being caught.
- 32-element vs 31-day arrays: Arrays are dimensioned to 32 (line 750–752) and the fill loop runs to 32, but only 31 elements are ever plotted. This is harmless but wastes a small amount of memory.
- Leap-year detection uses two-digit year:
LY = (YR/4) - INT(YR/4)works correctly for years divisible by 4 within the two-digit range, but would misidentify year 00 (2000) as a leap year coincidentally correctly, and year 00 (1900) — also correctly, since 1900 was not a leap year but the program has no century handling. - Month name PRINT statements (lines 701–712): Several month names are missing a comma before the closing quote (e.g.,
"FEBRUARY"""vs"JANUARY,"""), leading to inconsistent formatting in the display header.
Variable Summary
| Variable | Purpose |
|---|---|
A$ | Printer flag (“Y”/”N”) |
MN, DA, YR | Birth month, day, two-digit year |
BMN, BYR | Target month and year for display |
N$ | User’s name (4 letters) |
DA1 | Days remaining in birth year after birth date |
DA2 | Days in complete years between birth and target |
DA3 | Day-of-year offset for start of target month |
DAYT | Total elapsed days from birth to target month |
FUDGP/FUDGE/FUDGI | Phase column offsets for P/E/I cycles |
P(), E(), I() | Screen row arrays for each cycle’s 31 days |
LY, LYY | Leap-year fraction test and next leap year |
Content
Source Code
1 REM BIORHYTHM
2 PRINT "PROGRAM TO CALCULATE BIORHYTHMS FOR GIVEN MONTHS WHEN ZX81 IS PROVIDED WITH BIRTHDATE. COPYRIGHT-RONALD G. OBLANDER, ANN ARBOR,MICHIGAN 1982"
3 PRINT "IF PRINTER OUTPUT IS DESIRED, ENTER ""Y"",IF NOT ""N""";"--";
4 INPUT A$
5 PRINT A$
6 PRINT "ENTER MONTH OF BIRTH IN NUMERICAL FORM";"--";
10 INPUT MN
11 PRINT MN
15 PRINT "ENTER DAY OF MONTH OF BIRTH--";
20 INPUT DA
21 PRINT DA
25 PRINT "ENTER LAST TWO DIGITS OF YEAR OF BIRTH";"--";
30 INPUT YR
31 PRINT YR
35 PRINT "ENTER MONTH OF BIORHYTHMS DESIRED";"--";
36 INPUT BMN
37 PRINT BMN
42 PRINT "ENTER LAST TWO DIGITS OF YEAR OF BIORHYTHMS";"--";
43 INPUT BYR
44 PRINT BYR
52 IF BYR<YR THEN GOTO 59
53 IF MN<1 THEN GOTO 61
54 IF MN>12 THEN GOTO 61
55 IF BMN<1 THEN GOTO 61
56 IF BMN>12 THEN GOTO 61
57 IF DA>31 THEN GOTO 63
58 GOTO 65
59 PRINT "BIORHYTHMS BEFORE BIRTH ARE INVALID DUMMY-RERUN"
60 STOP
61 PRINT "INVALID MONTH NUMBER DUMB-BELL -RERUN"
62 STOP
63 PRINT "INVALID DAY NUMBER, IDIOT -RERUN"
64 STOP
65 PRINT "ENTER NAME(4 LETTERS ONLY)--";
66 INPUT N$
67 PRINT N$
76 FAST
77 LET LY=(YR/4)-INT (YR/4)
78 LET LYY=(INT (YR/4)+1)*4
79 IF LY=0 THEN GOTO 150
80 IF MN=1 THEN LET DA1=366-DA
85 IF MN=2 THEN LET DA1=335-DA
90 IF MN=3 THEN LET DA1=307-DA
95 IF MN=4 THEN LET DA1=277-DA
100 IF MN=5 THEN LET DA1=246-DA
105 IF MN=6 THEN LET DA1=216-DA
110 IF MN=7 THEN LET DA1=186-DA
115 IF MN=8 THEN LET DA1=154-DA
120 IF MN=9 THEN LET DA1=123-DA
125 IF MN=10 THEN LET DA1=93-DA
130 IF MN=11 THEN LET DA1=62-DA
135 IF MN=12 THEN LET DA1=32-DA
140 GOTO 250
150 IF MN>=3 THEN GOTO 90
155 IF MN=1 THEN LET DA1=367-DA
160 IF MN=2 THEN LET DA1=336-DA
165 GOTO 250
250 LET DA2=0
255 IF YR=BYR THEN GOTO 257
256 GOTO 260
257 PRINT "BIORHYTHMS IN YEAR OF BIRTH CANNOT BE CALCULATED-SORRY"
258 STOP
260 LET YR1=YR+1
265 IF YR1=BYR THEN GOTO 320
270 IF YR1=LYY THEN GOTO 305
280 LET DA2=DA2+365
285 LET YR1=YR1+1
290 IF YR1=BYR THEN GOTO 320
295 IF YR1=LYY THEN GOTO 305
300 GOTO 280
305 LET DA2=DA2+366
310 LET LYY=LYY+4
315 GOTO 285
320 IF YR1=LYY THEN GOTO 385
325 IF BMN=1 THEN LET DA3=0
330 IF BMN=2 THEN LET DA3=31
335 IF BMN=3 THEN LET DA3=59
340 IF BMN=4 THEN LET DA3=90
345 IF BMN=5 THEN LET DA3=120
350 IF BMN=6 THEN LET DA3=151
355 IF BMN=7 THEN LET DA3=181
360 IF BMN=8 THEN LET DA3=212
365 IF BMN=9 THEN LET DA3=243
370 IF BMN=10 THEN LET DA3=273
375 IF BMN=11 THEN LET DA3=304
380 IF BMN=12 THEN LET DA3=334
381 GOTO 405
385 IF BMN>2 THEN GOTO 395
390 GOTO 325
395 LET DA2=DA2+1
400 GOTO 325
405 LET DAYT=-1+DA1+DA2+DA3
406 POKE 16418,0
422 LET FUDGP=INT (.5+22-(DAYT/23-INT (DAYT/23))*23)
423 LET FUDGE=INT (.5+27-(DAYT/28-INT (DAYT/28))*28)
424 LET FUDGI=INT (.5+32-(DAYT/33-INT (DAYT/33))*33)
425 GOTO 745
690 SLOW
691 PRINT AT 11,0;"% % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % % "
692 IF BMN=12 OR BMN=1 OR BMN=3 OR BMN=5 OR BMN=7 OR BMN=8 OR BMN=10 THEN PRINT AT 10,0;" 1111111111222222222233"
693 IF BMN=12 OR BMN=1 OR BMN=3 OR BMN=5 OR BMN=7 OR BMN=8 OR BMN=10 THEN PRINT AT 12,0;" 1234567890123456789012345678901"
694 IF BMN=4 OR BMN=6 OR BMN=9 OR BMN=11 THEN PRINT AT 10,0;" 111111111122222222223"
695 IF BMN=4 OR BMN=6 OR BMN=9 OR BMN=11 THEN PRINT AT 12,0;" 123456789012345678901234567890"
696 IF BMN=2 THEN PRINT AT 10,0;" 1111111111222222222"
697 IF BMN=2 THEN PRINT AT 12,0;" 1234567890123456789012345678"
699 PRINT AT 0,0;"*******BIORHYTHMS FOR ";N$;"******"
700 PRINT AT 1,4;"FOR MONTH OF ";
701 IF BMN=1 THEN PRINT "JANUARY,""";BYR;
702 IF BMN=2 THEN PRINT "FEBRUARY""";BYR;
703 IF BMN=3 THEN PRINT "MARCH,""";BYR;
704 IF BMN=4 THEN PRINT "APRIL,""";BYR;
705 IF BMN=5 THEN PRINT "MAY,""";BYR;
706 IF BMN=6 THEN PRINT "JUNE,""";BYR;
707 IF BMN=7 THEN PRINT "JULY,""";BYR;
708 IF BMN=8 THEN PRINT "AUGUST,""";BYR;
709 IF BMN=9 THEN PRINT "SEPTEMBER,""";BYR;
710 IF BMN=10 THEN PRINT "OCTOBER,""";BYR;
711 IF BMN=11 THEN PRINT "NOVEMBER,""";BYR;
712 IF BMN=12 THEN PRINT "DECEMBER,""";BYR;
713 PRINT AT 23,0;"******BIRTHDATE:";MN;"/";DA;"/";YR;"******"
717 PRINT AT 21,2;"P";"=PHYS ";"E";"=EMOT ";"I";"=INTEL"
718 FOR S=2 TO 9
719 PRINT AT S,0;"%+"
720 PRINT AT S+11,0;"%-"
721 NEXT S
722 PRINT AT 22,6;"% % % % % =CRITICAL DAY"
723 PRINT AT 9,0;"%D"
724 PRINT AT 10,0;"%A"
725 PRINT AT 11,0;"%T"
726 PRINT AT 12,0;"%E"
727 GOTO 952
745 FAST
750 DIM P(32)
751 DIM E(32)
752 DIM I(32)
755 FOR P=0 TO 32
760 LET X=FUDGP+P
765 IF X>31 THEN GOTO 780
770 LET P(X+1)=11-5*SIN (P/11.5*PI)
775 GOTO 805
780 FOR Q=1 TO 31
785 LET XX=FUDGP-Q
790 IF XX<0 THEN GOTO 815
795 LET P(XX+1)=11+5*SIN (Q/11.5*PI)
800 NEXT Q
805 NEXT P
815 FOR E=0 TO 32
820 LET X1=FUDGE+E
825 IF X1>31 THEN GOTO 840
830 LET E(X1+1)=11-7*SIN (E/14*PI)
835 GOTO 865
840 FOR F=1 TO 31
845 LET XXX=FUDGE-F
850 IF XXX<0 THEN GOTO 900
855 LET E(XXX+1)=11+7*SIN (F/14*PI)
860 NEXT F
865 NEXT E
900 FOR I=0 TO 31
905 LET X2=FUDGI+I
910 IF X2>31 THEN GOTO 925
915 LET I(X2+1)=11-9*SIN (I/16.5*PI)
920 GOTO 946
925 FOR G=1 TO 36
930 LET X3=FUDGI-G
935 IF X3<0 THEN GOTO 947
940 LET I(X3+1)=11+9*SIN (G/16.5*PI)
945 NEXT G
946 NEXT I
947 CLS
948 GOTO 690
952 FOR P=1 TO 31
953 PRINT AT P(P),P;"P"
954 NEXT P
962 FOR P=1 TO 31
963 PRINT AT E(P),P;"E"
964 NEXT P
965 FOR P=1 TO 31
966 PRINT AT I(P),P;"I"
967 NEXT P
968 POKE 16418,12
970 IF A$="Y" THEN COPY
990 PRINT AT 0,0;"PRESS N-FOR NEXT MONTH, R-FOR RESTART (DIFFERENT BIRTHDATE) ORS-FOR STOP. "
991 IF INKEY$="" THEN GOTO 991
992 LET U$=INKEY$
993 IF U$="N" OR U$="R" OR U$="S" THEN GOTO 998
994 PRINT AT 0,0;"INVALID ENTRY -- REENTER. "
995 PAUSE 30
996 PRINT AT 0,0;" "
997 GOTO 990
998 IF U$="N" THEN GOTO 1061
999 IF U$="R" THEN GOTO 1010
1000 STOP
1010 CLS
1011 GOTO 2
1061 LET BMN=BMN+1
1062 IF BMN<=12 THEN GOTO 76
1063 LET BMN=BMN-12
1064 LET BYR=BYR+1
1066 GOTO 76
Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.




