commit 09fbd26bda3ce80c009d5f1706f8d82c63f5ebbe parent 21db3850a9649fd8e301cdf65e4101b713f41cfd Author: Gerd Beuster <gerd@frombelow.net> Date: Sat, 22 May 2021 16:22:30 +0200 Support for C programming language & example C programs added Diffstat:
83 files changed, 4121 insertions(+), 1050 deletions(-)
diff --git a/.gitignore b/.gitignore @@ -15,3 +15,13 @@ sw/**/*.lst roms/**/*.bin roms/**/*.l roms/**/*.lst +contrib/**/*.bin +contrib/**/*.l +contrib/**/*.lst +contrib/c/*/*.map +contrib/c/*/*.o +contrib/c/*/*.s +sw/c/cc65_eris/eris2010.lib +sw/c/*/*.map +sw/c/*/*.o +sw/c/*/*.s diff --git a/README.md b/README.md @@ -6,22 +6,21 @@ Eris 2010 is homebrew 8-Bit computer in the style of the microcomputers of the 1970/80s. It is build around the WDC 65C02 MPU. This is a slightly updated version of the legendary MOS 6502 used for example in the Commodore VIC20 and the Apple I and ][. The other -main components are 32 KB (parallel) RAM and 8 KB EEPROM. While the -old home computers from the 1970/80s came with a keyboard and -connected to a TV or monitor, this design provides a serial -communication interface. Therefore it is more similar to modern -microcontrollers or very early home computers like the Altair 8800 -than to later computers of the home computer era like the Commodore 64 -or the Apple ][. The serial interface is provided by a 6551 ACAI. This -chip has been used in computers from the era like the Commodore PET -and the Commodore Plus/4 to connect a modem. In the Eris 2010, the -6551 ACAI connects to a (modern) USB2Serial adapter. This allows to -use a standard laptop or desktop computer as a terminal for the 8-bit -machine. +main components are 32 KB RAM and 8 KB EEPROM. While the old home +computers from the 1970/80s came with a keyboard and connected to a TV +or monitor, this design provides a serial communication +interface. Therefore it is more similar to modern microcontrollers or +very early home computers like the Altair 8800 than to later computers +of the home computer era like the Commodore 64 or the Apple ][. The +serial interface is provided by a 6551 ACAI. This chip has been used +in computers from the era like the Commodore PET and the Commodore +Plus/4 to connect a modem. In the Eris 2010, the 6551 ACAI connects to +a (modern) USB2Serial adapter. This allows to use a standard laptop or +desktop computer as a terminal for the 8-bit machine. The second auxiliary chip is a 6522 VIA. Among other things, this chip provides a bunch of GPIO lines. A few of them are used to connect an -SD card reader, providing a mass storage device to the Eris 2010. The +SD card reader, providing a mass storage device to Eris 2010. The remaining lines are freely programmable I/O ports. SD cards are written in a very simple, proprietary data format. When the computer is powered, the "operating system" in EEPROM presents a list of the @@ -48,20 +47,21 @@ repository. ## Software -Directory `sw/` contains the game of Tic-Tac-Toe (no fan of WarGames -should go without one), the famous 10 PRINT program (check out [this +Eris 2010 can be programmed in assembler and C. Directory `sw/` +contains the game of Tic-Tac-Toe (no fan of WarGames should go without +one), the famous 10 PRINT program (check out [this book](https://10print.org/ "10 PRINT") on the cultural significance of 10 PRINT if you do not know it already), and some test programs. The `contrib/` directory contains additional third party software ported to Eris 2010. Besides Wozmon, Steve Wozniak's "operating system" for the Apple I, this repository contains a port of Microchess, the first -commercial game for microcomputers. The contrib directory also -contains a port of Tiny BASIC, so you can even program the system in -BASIC, just like in the old days. +commercial game for microcomputers, some other games, and a port of +Eliza. The contrib directory also contains a port of Tiny BASIC, so +you can even program the system in BASIC, just like in the old days. All components of the system (except for the third party software in -contrib) are free soft- and hardware. You can find all source code in -the repository linked at the top of this page. +`contrib/`) are free soft- and hardware. You can find all source code +in the repository linked at the top of this page. ---------------------------------------------------------------- @@ -105,13 +105,16 @@ When putty starts, click the radio button "Serial". Set "Serial line" to "COM3", and "Speed" to "19200". Click "Open". In the terminal windows, type any character or push the reset button on Eris 2010. You should now be connected to the computer. If this does not work, try -"COM1", "COM2", ... for "Serial Line". Note that some programs -(e.g. 10print will not work, because putty does not support Unicode). +"COM1", "COM2", ... for "Serial Line". + +The default font of PuTTY does not support the unicode characters ued +by the 10 PRINT program. I order to run this program, choose +"Window/Appearance" and change the font to e.g. @NSimSun. ## Running programs You should now see a choice of programs. Typing a number starts the -corresponding program. The three most interesting programs are: +corresponding program. The more interesting programs are: ### Tic-Tac-Toe @@ -126,12 +129,33 @@ game. Microchess is third party software. -### Tiny BASIC +### TINY BASIC All input must be in caps! Tiny BASIC is third party software. +## Abandoned Farmhouse + +A small text adventure game. + +## Eliza + +A version of Weizenbaum's famous ELIZA. This version diverges quite a +bit from Weizenbaum's original program, which was more advanced. + +## Mastermind + +A version of Mastermind. There are some divergences from the original +rules: When you a number appears once in the code but the player +guesses it multiple times,"+" will be output for every wrong spot of +the number. + +## Wumpus + +Hunt the Wumpus, a classical computer game: +<https://en.wikipedia.org/wiki/Hunt_the_Wumpus>. + ---------------------------------------------------------------- # Developer Documentation @@ -140,16 +164,16 @@ Tiny BASIC is third party software. - `case/` - Minimal case consisting of top and bottom plates for laser cutting. - `contrib/` - Third party software. See `contrib/README.md` for a description. -- `hw/` - Hardware description (pcb not complete yet) +- `hw/` - Hardware description - `tools/` - PC programs to upload programs and write SD card - `roms/` - ROM images. See `roms/README.md` for a description. -- `sw/` - "Userland" software to be loaded into RAM by a suitable ROM. See - `sw/README.md` for a description. +- `sw/` - "Userland" software to be loaded into RAM by a suitable ROM. Eris2010 + can be programmed in assembler and C. ## Main Components The computer is based on a 65C02 running at 4 Mhz with 32K RAM -(AS6C62256-55PCN) and 8 K EEPROM (AT28C64B-15PU). A W65C51N ACAI +(AS6C62256-70PCN) and 8 K EEPROM (AT28C64B-15PU). A W65C51N ACAI provides a serial communication interface. A VIA 65C22 provides a GPIO interface. Four of the lines connect an SD card reader via SPI. Bus logic is provided by a ATF16V8B EEPLD. The reset logic is based on a @@ -172,6 +196,33 @@ device is active when `io_select5` is high and `io_select4` low. The fourth I/O device is active when `io_select5` is low and `io_select4` is high. See `hw/bus_logic/BUS_LOGIC.PLD` for details. +|Area |Content |Location | +|--- |:----------:|--------------:| +|ROM | OS | $E000 - $FFFF | +| | | | +|IO | EXP2 | $D800 - $DFFF | +| | EXP1 | $D000 - $D7FF | +| | VIA | $C800 - $CFFF | +| | ACIA | $C000 - $C7FF | +| | | | +|RAM | IRQ Vector | $7FFE - $7FFF | +| | Heap | ... - $7FFD | +| | ... | ... | +| | Data Stack | ... | +| | Program | $0200 - ... | +| | HW Stack | $0100 - $01FF | +| | Zero Page | $0000 - $00FF | + +The reset vector points to $E000, the begin of the ROM. Programs are +loaded to $0200. The IRQ handler jumps to the address stored in RAM at +$7FFE. For assembler programs, the data stack is located right after +the program code and grows up, as shown in the diagram above. For C +programs, the RAM layout is different. They use two stacks in addition +to the hardware stack: The assembler stack (used by assembler +functions in ROM) is located at the top of the RAM below the IRQ +vector ($7DFE - $7FFD), below it, the C stack occupies $6DFE - +$7DFD. Everything in between the program and the stacks is heap. + ## Reset logic Resets can be triggered in two ways: A reset button is connected to an @@ -179,16 +230,16 @@ NE555 in a monostable configuration. Alternatively, a reset can be triggered by setting DTR of the serial interface to high. The latter is used to automatically trigger a reset before program upload. `tools/boot.py` uses this to trigger a reset and get Eris 2010 -ready for progrm upload. The disadvantage of wiring DTR to rest is -that Eris 2010 ony operates when a terminal is connected; when no +ready for program upload. The disadvantage of wiring DTR to rest is +that Eris 2010 only operates when a terminal is connected; when no terminal is connected, it stays in reset mode. Therefore a hardware switch allows to disconnect DTR from the reset line, allowing Eris 2010 to run even if no terminal is connected. In this configuration, resets for program upload have to be triggered manually. See also -section Boot Sequence. +section Booting. Another way to run Eris 2010 even if no terminal program is running on -your main computer is to to configure your computer's serial interface +your main computer is to configure your computer's serial interface to keep DTR low at all times (Linux: `stty -F <serial interface> -hup`). @@ -201,21 +252,21 @@ The "standard" ROM is `roms/os/os.bin`. This ROM includes the standard library (for serial communication, accessing SD card, RNG, ...). It provides three methods to load a program: -- Via serial line +### Via serial line After a reset, the ROM listens for a serial data transmission at 19200 -BPS 8N1. If byte $ff is sent, the download sequence is initated. The +BPS 8N1. If byte $ff is sent, the download sequence is initiated. The next byte is the number of half-blocks of 256 bit to be loaded. (Block size is 512 bit, because this is the block size of SD cards.) The number of half-blocks is followed by the data. Data is stored at -$0200. Once the upload is completed. The upload program returns a +$0200. Once the upload is completed, the upload program returns a two-byte checksum. The first byte is the sum of all bytes transmitted mod 256. The second byte is the xor of all bytes transmitted. The upload program than starts executing the loaded program at $0200. On the PC, use `tools/boot.py` for upload. -- From SD Card +### From SD Card The boot ROM includes `sw/load_from_card/load_from_card.asm`, the program to load programs from SD card. This program is executed when @@ -224,12 +275,12 @@ no program is transmitted via serial line. SD cards can store up to 10 programs. The filesystem format is as follows: -Block $00000000 is loaded on initialization. The two byte should be -$E215. This indicates the file sytem type. The third byte should be -$00. This is the version of the file system. The fourth byte is the -number of the program to start automatically, or $ff. In case of $ff, -a menu of the programs on the card is presented for the user to -choose. +Block $00000000 is loaded on initialization. The first two bytes +should be $E215. This indicates the filesystem type. The third byte +should be $00. This is the version of the file system. The fourth byte +is the number of the program to start automatically, or $ff. In case +of $ff, a menu of the programs on the card is presented for the user +to choose. The storage space for the first program starts at $00000001. The storage space for the second program starts at $00010001, for the @@ -281,12 +332,12 @@ An ATF16V8B-15PU EEPLD is used for the bus logic. It is programmed in Both the EEPROM and the EEPLD can be burned with [minipro](https://gitlab.com/DavidGriffith/minipro/). -### Software +### Assembler -All software for Eris 2010 has been assembled with 64tass. The -Makefiles generate binary code both for Eris 2010 and -Symon. [Symon](https://github.com/sethm/symon), a 6502 emulator -written in Java, has been used for debugging. +All core parts of Eris 2010 have written in assembler. They can be +translated with 64tass. The Makefiles generate binary code both for +Eris 2010 and Symon. [Symon](https://github.com/sethm/symon), a 6502 +emulator written in Java, has been used for debugging. ## Standard Library @@ -299,34 +350,28 @@ documentation and `sw/stack_test/stack_test.asm` for examples. The following functionality is provided: -- Serial communication interface - +- Serial communication interface All serial communication is conducted at 19200BPS with 8N1. Various input and output functions are provided, including a function that initializes the RNG (see LFSR) by counting ticks when waiting for user input. -- ANSI Terminal - +- ANSI Terminal On top of the serial communication interface, the core ANSI escape sequences are supported. -- LFSR - +- LFSR A 16 bit LFSR with maximum period length is provided as PRNG. -- SPI - +- SPI SPI messages can be send via the VIA. -- SD Card - +- SD Card SD cards can accessed in SPI mode. All addresses are 32 bit, referring to 512 bit blocks on the card. Note that this implementation does not work with all cards. See section [bugs](#bugs) below. -- Data Stack - +- Data Stack A data stack in software for local variables and parameter passing. In difference to most stacks, the stack grows from bottom to top. The recommended memory layout is program code, followed by heap (if @@ -341,6 +386,51 @@ represented by integers and accessed by `lda_LOCAL \<number\>`, parameters. Refer to `sw/stack_test/stack_test.bin` and `sw/load_from_card/load_from_card.asm` for examples. +### C + +You can program Eris 2010 in C using the CC65 C compiler, available at +<https://cc65.github.io/>. Suitable configuration and Makefiles, as +well as ports of the conio and dio libraries, are located in directory +`sw/c/cc65_eris`. You have to run `make` once in this directory in +order to generate `eris2010.lib`. After that, you can compile the +programs in the other subdirectories. The programs here are simple +test programs. Check out `contrib/c/` for ports of (more or less) +useful programs. + +#### The conio Library + +The conio library for console input/output has been ported to use the +serial connection. Since the serial connection is the main I/O +connection of Eris 2010, conio and not the serial library is used to +access it. Some functions of conio have not been ported because the +functionality is not available via a serial line with ANSI encoding: + +- bgcolor +- bordercolor +- cpeekc +- cpeekcolor +- cpeekrevers +- cpeeks + +#### The dio Library + +The dio library provides low level file system access. dio uses 16 bit +sector numbers. Since SD cards use 32 bit sector numbers, we use a +hack to be able to access all sectors of the SD card while maintaining +compatibility to the dio library: The upper 16 bits of the sector +number are encoded as the device handle. + +In order to access the SD card, you have to call `dio_open` +once. Neither the `device` number you pass into this function nor the +handle returned matter. `dio_write` and `dio_read` treat `handle` as +an unsigned int indicating the upper 16 bits of the sector +number. (`dio_write_verify` is not implemented.) + +Due to our file system structure, this `handle` is identical to the +application number of the application whose data you are +accessing. The application number is a (the) parameter of `main`. Use +it to make sure that your program only writes to its own storage space. + ## Tools ### kfs.py @@ -360,24 +450,27 @@ Triggers a reset by toggling DTR. The main purpose of the case is to expose the main board. :-) Therefore it just consists of a top and bottom acrylic plates, -seperated by spacer bolts. Laser cutter cutout files are located in directory `case/` +separated by spacer bolts. Laser cutter cutout files are located in +directory `case/` ## Bugs The communication protocol for SD cards is rather complex. The -implementation for Eris 2010 worked with 4 out of 5 cards tested. If +implementation for Eris 2010 worked with 5 out of 6 cards tested. If it does not work with your card, try a different card (or fix the protocol implementation). :-) ## Changelog +- v1.1 +Support for C programming language. - v1.0 First public version. ## Copyright and License -The software in directory contrib/ comes from third parties. Check the -subdirectories of contrib/ for author, copyright, and licensing +The software in directory `contrib/` comes from third parties. Check the +subdirectories of `contrib/` for author, copyright, and licensing information. Everything else: diff --git a/case/case.svg b/case/case.svg @@ -26,8 +26,8 @@ inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1.4" - inkscape:cx="391.91214" - inkscape:cy="849.42529" + inkscape:cx="196.83374" + inkscape:cy="832.4268" inkscape:document-units="mm" inkscape:current-layer="g35239" showgrid="false" @@ -36,15 +36,31 @@ inkscape:window-x="0" inkscape:window-y="0" inkscape:window-maximized="1" - inkscape:snap-grids="false" - inkscape:snap-to-guides="false" + inkscape:snap-grids="true" + inkscape:snap-to-guides="true" showguides="true" inkscape:guide-bbox="true" inkscape:snap-others="true" - inkscape:object-nodes="false" - inkscape:snap-nodes="false" + inkscape:object-nodes="true" + inkscape:snap-nodes="true" inkscape:snap-global="true" - inkscape:snap-object-midpoints="true" /> + inkscape:snap-object-midpoints="true" + inkscape:snap-bbox="true" + inkscape:bbox-paths="true" + inkscape:bbox-nodes="true" + inkscape:snap-bbox-edge-midpoints="true" + inkscape:snap-bbox-midpoints="true" + inkscape:object-paths="true" + inkscape:snap-intersection-paths="true" + inkscape:snap-smooth-nodes="true" + inkscape:snap-midpoints="true" + inkscape:snap-text-baseline="true" + inkscape:snap-center="true" + inkscape:snap-page="true"> + <inkscape:grid + type="xygrid" + id="grid2436" /> + </sodipodi:namedview> <metadata id="metadata14297"> <rdf:RDF> @@ -8005,7 +8021,7 @@ inkscape:groupmode="layer" id="layer4" inkscape:label="Bottom Plate" - style="display:none"> + style="display:inline"> <rect style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:none;fill-opacity:1;fill-rule:nonzero;stroke:#0000ff;stroke-width:0.01;stroke-linecap:square;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;enable-background:accumulate" id="rect35227" @@ -8069,7 +8085,7 @@ inkscape:groupmode="layer"> <path style="fill:none;fill-rule:evenodd;stroke:#0000ff;stroke-width:0.01;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" - d="m 14.866867,10.887961 v 86.32807 H 73.499118 V 80.578491 H 171.0525 v 16.63754 h 11.60449 v -86.32807 h -50.69266 v 11.75986 h -99.157 v -11.75986 z" + d="m 14.866867,10.887961 v 86.32807 h 58.632251 l 0.0059,-10.75761 h 97.547472 l 0,10.75761 h 11.60449 v -86.32807 h -50.69266 v 11.75986 H 32.807324 v -11.75986 z" id="path35254" inkscape:connector-curvature="0" sodipodi:nodetypes="ccccccccccccc" /> diff --git a/contrib/Makefile.common b/contrib/Makefile.common @@ -1,18 +0,0 @@ -ERISPATH=../.. - -CFLAGS=-C --line-numbers --tab-size=1 -Wall -c -b -I$(ERISPATH)/roms/os/ -VERSION="$(shell git describe --tags)" - -all: $(TARGET).bin $(TARGET)_symon.bin - -%.bin: %.asm - 64tass $(CFLAGS) $(DEFS) -DVERSION="'$(VERSION)'" -DSYMON=false -l $(TARGET).l -L $(TARGET).lst "$<" -o "$@" - -%_symon.bin: %.asm - 64tass $(CFLAGS) $(DEFS) -DVERSION="'$(VERSION)'" -DSYMON=true -l $(TARGET)_symon.l -L $(TARGET)_symon.lst "$<" -o "$@" - -upload: $(TARGET).bin - ../../eris2010/tools/boot.py $(TARGET).bin - -clean: - rm -f *.bin *.l *.lst diff --git a/contrib/README.md b/contrib/README.md @@ -1,28 +0,0 @@ -# Eris 2010 Contrib - -This repository contains third party software ported to the Eris 2010 -computer. Contact the original authors for inquiries on licensing. - -## wozmon/ - -Port of Wozmon, Steve Wozniak's "operating system" for the Apple -I. Instructions on how to use it can be found here: -https://www.sbprojects.net/projects/apple1/wozmon.php - -Command "S" has been added which prints the stack pointer. - -## microchess/ - -Port of Microchess, the first commerical game for microcomputers. You -can find plenty of information about Micorchess on the author's web -page: http://www.benlo.com/microchess/ - -In order to play, press 'c' to set up a new board. Enter moves by -given the field numbers followed by enter, e.g. '6444'<Enter>. Push -'p' for the next move of the computer. You can swith the board with -'e'. 'q' quits the game. - - -## Tiny-BASIC/ - -Port of Tiny BASIC. All input is expected in upper case! diff --git a/contrib/asm/Makefile.common b/contrib/asm/Makefile.common @@ -0,0 +1,18 @@ +ERISPATH=../../.. + +CFLAGS=-C --line-numbers --tab-size=1 -Wall -c -b -I$(ERISPATH)/roms/os/ +VERSION="$(shell git describe --tags)" + +all: $(TARGET).bin $(TARGET)_symon.bin + +%.bin: %.asm + 64tass $(CFLAGS) $(DEFS) -DVERSION="'$(VERSION)'" -DSYMON=false -l $(TARGET).l -L $(TARGET).lst "$<" -o "$@" + +%_symon.bin: %.asm + 64tass $(CFLAGS) $(DEFS) -DVERSION="'$(VERSION)'" -DSYMON=true -l $(TARGET)_symon.l -L $(TARGET)_symon.lst "$<" -o "$@" + +upload: $(TARGET).bin + ../../eris2010/tools/boot.py $(TARGET).bin + +clean: + rm -f *.bin *.l *.lst diff --git a/contrib/asm/README.md b/contrib/asm/README.md @@ -0,0 +1,29 @@ +# Eris 2010 Contrib Assembler + +This repository contains third party assembler programs ported to the +Eris 2010 computer. Contact the original authors for inquiries on +licensing. + +## wozmon/ + +Port of Wozmon, Steve Wozniak's "operating system" for the Apple +I. Instructions on how to use it can be found here: +https://www.sbprojects.net/projects/apple1/wozmon.php + +Command "S" has been added which prints the stack pointer. + +## microchess/ + +Port of Microchess, the first commerical game for microcomputers. You +can find plenty of information about Micorchess on the author's web +page: http://www.benlo.com/microchess/ + +In order to play, press 'c' to set up a new board. Enter moves by +given the field numbers followed by enter, e.g. '6444'<Enter>. Push +'p' for the next move of the computer. You can swith the board with +'e'. 'q' quits the game. + + +## Tiny-BASIC/ + +Port of Tiny BASIC. All input is expected in upper case! diff --git a/contrib/Tiny-BASIC/Makefile b/contrib/asm/Tiny-BASIC/Makefile diff --git a/contrib/Tiny-BASIC/README.md b/contrib/asm/Tiny-BASIC/README.md diff --git a/contrib/Tiny-BASIC/TBuserMan.txt b/contrib/asm/Tiny-BASIC/TBuserMan.txt diff --git a/contrib/Tiny-BASIC/tinybasic.asm b/contrib/asm/Tiny-BASIC/tinybasic.asm diff --git a/contrib/microchess/Makefile b/contrib/asm/microchess/Makefile diff --git a/contrib/microchess/microchess.asm b/contrib/asm/microchess/microchess.asm diff --git a/contrib/wozmon/Makefile b/contrib/asm/wozmon/Makefile diff --git a/contrib/wozmon/wozmon.asm b/contrib/asm/wozmon/wozmon.asm diff --git a/contrib/c/README.md b/contrib/c/README.md @@ -0,0 +1,26 @@ +# Eris 2010 Contrib C + +This repository contains third party C programs ported to the Eris +2010 computer. Contact the original authors for inquiries on +licensing. + +## abandoned_farmhouse/ + +A small text adventure game. + +## eliza/ + +A version of Weizenbaum's famous ELIZA. This version diverges quite a +bit from Weizenbaum's original program, which was more advanced. + +## mmind/ + +A version of Mastermind. There are some divergences from the original +rules: When you a number appears once in the code but the player +guesses it multiple times,"+" will be output for every wrong spot of +the number. + +## wumpus/ + +Hunt the Wumpus, a classical computer game: +<https://en.wikipedia.org/wiki/Hunt_the_Wumpus>. diff --git a/contrib/c/abandoned_farmhouse/LICENSE-2.0.txt b/contrib/c/abandoned_farmhouse/LICENSE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/contrib/c/abandoned_farmhouse/Makefile b/contrib/c/abandoned_farmhouse/Makefile @@ -0,0 +1,3 @@ +TARGET=adventure + +include ../../../sw/c/cc65_eris/Makefile.common diff --git a/contrib/c/abandoned_farmhouse/README.txt b/contrib/c/abandoned_farmhouse/README.txt @@ -0,0 +1,39 @@ +The Abandoned Farmhouse Adventure is a text adventure game in the +spirit of similar games that ran on 8-bit microcomputers of the 1970s +and 80s or the more ambitious Colossal Cave adventure that originally +only ran on mainframes and minicomputers. + +The plot and game should be self-explanatory. Figuring it out is the +point of the game. + +I started writing it in BASIC but some things were very awkward to do +efficiently in Apple 1 BASIC. I also considered writing it in assembly +language, but ended up writing it in C because the excellent CC65 +assembler worked very well for me. + +It was written to run on the Apple Replica 1 although it is in +portable C and should run on any system with a C compiler (I did most +of the development and testing on a Linux system). + +Because it was intended to run on the Replica 1 it was kept small and +efficient to run within the 32K memory limit and only use uppercase +characters and fit on a 40x24 character screen. Some code looks a +little unusual because it makes some optimizations for size and speed, +e.g. chars instead of ints, pre versus post increment/decrement. It is +a little too big to fit in an 8K EEPROM. It also won't run on an +original Apple 1 with 4K of memory but I am willing to port it if +someone sends me a system :-) + +The source is included and under an Apache license so you can modify +and adapt the code if you wish. Much of the code is data-driven and +could be used to implement an entirely different adventure just by +changing the map, strings, and some of the logic that handles special +actions. + +Oh and by the way, the farm described here is based on a real +farmhouse where my father lived many years ago, right down to the +layout of most of the rooms. And I also have grandson who was +almost 3 years old at the time I wrote this. + +Jeff Tranter <tranter@pobox.com> +http://jefftranter.blogspot.ca/ diff --git a/contrib/c/abandoned_farmhouse/TODO.txt b/contrib/c/abandoned_farmhouse/TODO.txt @@ -0,0 +1,4 @@ +To Do +----- + +- try to shrink down to 8K for EPROM or Apple 1 Mimeo diff --git a/contrib/c/abandoned_farmhouse/adventure.c b/contrib/c/abandoned_farmhouse/adventure.c @@ -0,0 +1,849 @@ +/* + * Source: https://github.com/jefftranter/6502 + * + * Minor changes by Gerd Beuster (gerd@frombelow.net) in order to get + * it to work on Eris 2010. + * + * The Abandoned Farm House Adventure + * + * Jeff Tranter <tranter@pobox.com> + * + * Written in standard C but designed to run on the Apple Replica 1 + * or Apple II using the CC65 6502 assembler. + * + * Copyright 2012-2015 Jeff Tranter + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Revision History: + * + * Version Date Comments + * ------- ---- -------- + * 0.0 13 Mar 2012 First alpha version + * 0.1 18 Mar 2012 First beta version + * 0.9 19 Mar 2012 First public release + * 1.0 06 Sep 2015 Lower case and other Apple II improvements. + * + */ + +#include <ctype.h> +#include <stdlib.h> +#include <string.h> +#ifdef __CC65__ +#include <conio.h> +#endif + +/* CONSTANTS */ + +/* Maximum number of items user can carry */ +#define MAXITEMS 5 + +/* Number of locations */ +#define NUMLOCATIONS 32 + +/* TYPES */ + +/* To optimize for code size and speed, most numbers are 8-bit chars when compiling for CC65. */ +#ifdef __CC65__ +typedef char number; +#else +typedef int number; +#endif + +/* Directions */ +typedef enum { + North, + South, + East, + West, + Up, + Down +} Direction_t; + +/* Items */ +typedef enum { + NoItem, + Key, + Pitchfork, + Flashlight, + Lamp, + Oil, + Candybar, + Bottle, + Doll, + ToyCar, + Matches, + GoldCoin, + SilverCoin, + StaleMeat, + Book, + Cheese, + OldRadio, + LastItem=OldRadio +} Item_t; + +/* Locations */ +typedef enum { + NoLocation, + Driveway1, + Driveway2, + Driveway3, + Driveway4, + Driveway5, + Garage, + WorkRoom, + Hayloft, + Kitchen, + DiningRoom, + BottomStairs, + DrawingRoom, + Study, + TopStairs, + BoysBedroom, + GirlsBedroom, + MasterBedroom, + ServantsQuarters, + LaundryRoom, + FurnaceRoom, + VacantRoom, + Cistern, + Tunnel, + Woods24, + Woods25, + Woods26, + WolfTree, + Woods28, + Woods29, + Woods30, + Woods31, +} Location_t; + +/* TABLES */ + +/* Names of directions */ +char *DescriptionOfDirection[] = { + "north", "south", "east", "west", "up", "down" +}; + +/* Names of items */ +char *DescriptionOfItem[LastItem+1] = { + "", + "key", + "pitchfork", + "flashlight", + "lamp", + "oil", + "candybar", + "bottle", + "doll", + "toy car", + "matches", + "gold coin", + "silver coin", + "stale meat", + "book", + "cheese", + "old radio", +}; + +/* Names of locations */ +char *DescriptionOfLocation[NUMLOCATIONS] = { + "", + "in the driveway near your car", + "in the driveway", + "in front of the garage", + "in front of the barn", + "at the door to the house", + "in the garage", + "in the workroom of the barn", + "in the hayloft of the barn", + "in the kitchen", + "in the dining room", + "at the bottom of the stairs", + "in the drawing room", + "in the study", + "at the top of the stairs", + "in a boy's bedroom", + "in a girl's bedroom", + "in the master bedroom next to\r\na bookcase", + "in the servant's quarters", + "in the basement laundry room", + "in the furnace room", + "in a vacant room next to a\r\nlocked door", + "in the cistern", + "in an underground tunnel. There are rats here", + "in the woods near a trapdoor", + "in the woods", + "in the woods", + "in the woods next to a tree", + "in the woods", + "in the woods", + "in the woods", + "in the woods", +}; + +/* DATA */ + +/* Inventory of what player is carrying */ +Item_t Inventory[MAXITEMS]; + +/* Location of each item. Index is the item number, returns the location. 0 if item is gone */ +Location_t locationOfItem[LastItem+1]; + +/* Map. Given a location and a direction to move, returns the location it connects to, or 0 if not a valid move. Map can change during game play. */ +Direction_t Move[NUMLOCATIONS][6] = { + /* N S E W U D */ + { 0, 0, 0, 0, 0, 0 }, /* 0 */ + { 2, 0, 0, 0, 0, 0 }, /* 1 */ + { 4, 1, 3, 5, 0, 0 }, /* 2 */ + { 0, 0, 6, 2, 0, 0 }, /* 3 */ + { 7, 2, 0, 0, 0, 0 }, /* 4 */ + { 0, 0, 2, 9, 0, 0 }, /* 5 */ + { 0, 0, 0, 3, 0, 0 }, /* 6 */ + { 0, 4, 0, 0, 8, 0 }, /* 7 */ + { 0, 0, 0, 0, 0, 7 }, /* 8 */ + { 0,10, 5, 0, 0,19 }, /* 9 */ + { 9, 0, 0,11, 0, 0 }, /* 10 */ + { 0, 0,10,12,14, 0 }, /* 11 */ + { 13, 0,11, 0, 0, 0 }, /* 12 */ + { 0,12, 0, 0, 0, 0 }, /* 13 */ + { 16, 0,15,17, 0,11 }, /* 14 */ + { 0, 0, 0,14, 0, 0 }, /* 15 */ + { 0,14, 0, 0, 0, 0 }, /* 16 */ + { 0, 0,14, 0, 0, 0 }, /* 17 */ + { 0, 0, 0, 0, 0,13 }, /* 18 */ + { 0, 0, 0,20, 9, 0 }, /* 19 */ + { 21, 0,19, 0, 0, 0 }, /* 20 */ + { 0,20, 0,22, 0, 0 }, /* 21 */ + { 0, 0,21, 0, 0, 0 }, /* 22 */ + { 24,21, 0, 0, 0, 0 }, /* 23 */ + { 29,23, 0,26, 0, 0 }, /* 24 */ + { 26, 0,24, 0, 0, 0 }, /* 25 */ + { 27,25,29, 0, 0, 0 }, /* 26 */ + { 0,26,28, 0, 0, 0 }, /* 27 */ + { 0,29,31,27, 0, 0 }, /* 28 */ + { 28,24,30,26, 0, 0 }, /* 29 */ + { 31, 0, 0,29, 0, 0 }, /* 30 */ + { 0,30, 0,29, 0, 0 }, /* 31 */ +}; + +/* Current location */ +number currentLocation; + +/* Number of turns played in game */ +int turnsPlayed; + +/* True if player has lit the lamp. */ +number lampLit; + +/* True if lamp filled with oil. */ +number lampFilled; + +/* True if player ate food. */ +number ateFood; + +/* True if player drank water. */ +number drankWater; + +/* Incremented each turn you are in the tunnel. */ +number ratAttack; + +/* Tracks state of wolf attack */ +number wolfState; + +/* Set when game is over */ +number gameOver; + +const char *introText = " Abandoned Farmhouse Adventure\r\n By Jeff Tranter\r\n\r\nYour three-year-old grandson has gone\r\nmissing and was last seen headed in the\r\ndirection of the abandoned family farm.\r\nIt's a dangerous place to play. You\r\nhave to find him before he gets hurt,\r\nand it will be getting dark soon...\r\n"; + +const char *helpString = "Valid commands:\r\ngo east/west/north/south/up/down \r\nlook\r\nuse <object>\r\nexamine <object>\r\ntake <object>\r\ndrop <object>\r\ninventory\r\nhelp\r\nquit\r\nYou can abbreviate commands and\r\ndirections to the first letter.\r\nType just the first letter of\r\na direction to move.\r\n"; + +/* Line of user input */ +char buffer[40]; + +// gb: Not available in conio, therefore added. +void gets(char *s) { + char c; + while ((c = cgetc()) != '\r') { + cputc(c); + *s = c; + s++; + } + *s = '\0'; + cputs("\r\n"); +}; + +/* Clear the screen */ +void clearScreen() +{ +#if defined(__APPLE2__) + clrscr(); +#else + number i; + for (i = 0; i < 24; ++i) + cprintf("\r\n"); +#endif +} + +/* Return 1 if carrying an item */ +number carryingItem(char *item) +{ + number i; + + for (i = 0; i < MAXITEMS; i++) { + if ((Inventory[i] != 0) && (!strcasecmp(DescriptionOfItem[Inventory[i]], item))) + return 1; + } + return 0; +} + +/* Return 1 if item it at current location (not carried) */ +number itemIsHere(char *item) +{ + number i; + + /* Find number of the item. */ + for (i = 1; i <= LastItem; i++) { + if (!strcasecmp(item, DescriptionOfItem[i])) { + /* Found it, but is it here? */ + if (locationOfItem[i] == currentLocation) { + return 1; + } else { + return 0; + } + } + } + return 0; +} + +/* Inventory command */ +void doInventory() +{ + number i; + int found = 0; + + cprintf("%s", "You are carrying:\r\n"); + for (i = 0; i < MAXITEMS; i++) { + if (Inventory[i] != 0) { + cprintf(" %s\r\n", DescriptionOfItem[Inventory[i]]); + found = 1; + } + } + if (!found) + cprintf(" nothing\r\n"); +} + +/* Help command */ +void doHelp() +{ + cprintf("%s", helpString); +} + +/* Look command */ +void doLook() +{ + number i, loc, seen; + + cprintf("You are %s.\r\n", DescriptionOfLocation[currentLocation]); + + seen = 0; + cprintf("You see:\r\n"); + for (i = 1; i <= LastItem; i++) { + if (locationOfItem[i] == currentLocation) { + cprintf(" %s\r\n", DescriptionOfItem[i]); + seen = 1; + } + } + if (!seen) + cprintf(" nothing special\r\n"); + + cprintf("You can go:"); + + for (i = North; i <= Down; i++) { + loc = Move[currentLocation][i]; + if (loc != 0) { + cprintf(" %s", DescriptionOfDirection[i]); + } + } + cprintf("\r\n"); +} + +/* Quit command */ +void doQuit() +{ + cprintf("%s", "Are you sure you want to quit (y/n)? "); + gets(buffer); + if (tolower(buffer[0]) == 'y') { + gameOver = 1; + } +} + +/* Drop command */ +void doDrop() +{ + number i; + char *sp; + char *item; + + /* Command line should be like "D[ROP] ITEM" Item name will be after after first space. */ + sp = strchr(buffer, ' '); + if (sp == NULL) { + cprintf("Drop what?\r\n"); + return; + } + + item = sp + 1; + + /* See if we have this item */ + for (i = 0; i < MAXITEMS; i++) { + if ((Inventory[i] != 0) && (!strcasecmp(DescriptionOfItem[Inventory[i]], item))) { + /* We have it. Add to location. */ + locationOfItem[Inventory[i]] = currentLocation; + /* And remove from inventory */ + Inventory[i] = 0; + cprintf("Dropped %s.\r\n", item); + ++turnsPlayed; + return; + } + } + /* If here, don't have it. */ + cprintf("Not carrying %s.\r\n", item); +} + +/* Take command */ +void doTake() +{ + number i, j; + char *sp; + char *item; + + /* Command line should be like "T[AKE] ITEM" Item name will be after after first space. */ + sp = strchr(buffer, ' '); + if (sp == NULL) { + cprintf("Take what?\r\n"); + return; + } + + item = sp + 1; + + if (carryingItem(item)) { + cprintf("Already carrying it.\r\n"); + return; + } + + /* Find number of the item. */ + for (i = 1; i <= LastItem; i++) { + if (!strcasecmp(item, DescriptionOfItem[i])) { + /* Found it, but is it here? */ + if (locationOfItem[i] == currentLocation) { + /* It is here. Add to inventory. */ + for (j = 0; j < MAXITEMS; j++) { + if (Inventory[j] == 0) { + Inventory[j] = i; + /* And remove from location. */ + locationOfItem[i] = 0; + cprintf("Took %s.\r\n", item); + ++turnsPlayed; + return; + } + } + + /* Reached maximum number of items to carry */ + cprintf("You can't carry any more. Drop something.\r\n"); + return; + } + } + } + + /* If here, don't see it. */ + cprintf("I see no %s here.\r\n", item); +} + +/* Go command */ +void doGo() +{ + char *sp; + char dirChar; + Direction_t dir; + + /* Command line should be like "G[O] N[ORTH]" Direction will be + the first letter after a space. Or just a single letter + direction N S E W U D or full directon NORTH etc. */ + + sp = strrchr(buffer, ' '); + if (sp != NULL) { + dirChar = *(sp+1); + } else { + dirChar = buffer[0]; + } + dirChar = tolower(dirChar); + + if (dirChar == 'n') { + dir = North; + } else if (dirChar == 's') { + dir = South; + } else if (dirChar == 'e') { + dir = East; + } else if (dirChar == 'w') { + dir = West; + } else if (dirChar == 'u') { + dir = Up; + } else if (dirChar == 'd') { + dir = Down; + } else { + cprintf("Go where?\r\n"); + return; + } + + if (Move[currentLocation][dir] == 0) { + cprintf("You can't go %s from here.\r\n", DescriptionOfDirection[dir]); + return; + } + + /* We can move */ + currentLocation = Move[currentLocation][dir]; + cprintf("You are %s.\r\n", DescriptionOfLocation[currentLocation]); + ++turnsPlayed; +} + +/* Examine command */ +void doExamine() +{ + char *sp; + char *item; + + /* Command line should be like "E[XAMINE] ITEM" Item name will be after after first space. */ + sp = strchr(buffer, ' '); + if (sp == NULL) { + cprintf("Examine what?\r\n"); + return; + } + + item = sp + 1; + ++turnsPlayed; + + /* Examine bookcase - not an object */ + if (!strcasecmp(item, "bookcase")) { + cprintf("You pull back a book and the bookcase\r\nopens up to reveal a secret room.\r\n"); + Move[17][North] = 18; + return; + } + + /* Make sure item is being carried or is in the current location */ + if (!carryingItem(item) && !itemIsHere(item)) { + cprintf("I don't see it here.\r\n"); + return; + } + + /* Examine Book */ + if (!strcasecmp(item, "book")) { + cprintf("It is a very old book entitled\r\n\"Apple 1 operation manual\".\r\n"); + return; + } + + /* Examine Flashlight */ + if (!strcasecmp(item, "flashlight")) { + cprintf("It doesn't have any batteries.\r\n"); + return; + } + + /* Examine toy car */ + if (!strcasecmp(item, "toy car")) { + cprintf("It is a nice toy car.\r\nYour grandson Matthew would like it.\r\n"); + return; + } + + /* Examine old radio */ + if (!strcasecmp(item, "old radio")) { + cprintf("It is a 1940 Zenith 8-S-563 console\r\nwith an 8A02 chassis. You'd turn it on\r\nbut the electricity is off.\r\n"); + return; + } + + /* Nothing special about this item */ + cprintf("You see nothing special about it.\r\n"); +} + +/* Use command */ +void doUse() +{ + char *sp; + char *item; + + /* Command line should be like "U[SE] ITEM" Item name will be after after first space. */ + sp = strchr(buffer, ' '); + if (sp == NULL) { + cprintf("Use what?\r\n"); + return; + } + + item = sp + 1; + + /* Make sure item is being carried or is in the current location */ + if (!carryingItem(item) && !itemIsHere(item)) { + cprintf("I don't see it here.\r\n"); + return; + } + + ++turnsPlayed; + + /* Use key */ + if (!strcasecmp(item, "key") && (currentLocation == VacantRoom)) { + cprintf("You insert the key in the door and it\r\nopens, revealing a tunnel.\r\n"); + Move[21][North] = 23; + return; + } + + /* Use pitchfork */ + if (!strcasecmp(item, "pitchfork") && (currentLocation == WolfTree) && (wolfState == 0)) { + cprintf("You jab the wolf with the pitchfork.\r\nIt howls and runs away.\r\n"); + wolfState = 1; + return; + } + + /* Use toy car */ + if (!strcasecmp(item, "toy car") && (currentLocation == WolfTree && wolfState == 1)) { + cprintf("You show Matthew the toy car and he\r\ncomes down to take it. You take Matthew\r\nin your arms and carry him home.\r\n"); + wolfState = 2; + return; + } + + /* Use oil */ + if (!strcasecmp(item, "oil")) { + if (carryingItem("lamp")) { + cprintf("You fill the lamp with oil.\r\n"); + lampFilled = 1; + return; + } else { + cprintf("You don't have anything to use it with.\r\n"); + return; + } + } + + /* Use matches */ + if (!strcasecmp(item, "matches")) { + if (carryingItem("lamp")) { + if (lampFilled) { + cprintf("You light the lamp. You can see!\r\n"); + lampLit = 1; + return; + } else { + cprintf("You can't light the lamp. It needs oil.\r\n"); + return; + } + } else { + cprintf("Nothing here to light\r\n"); + } + } + + /* Use candybar */ + if (!strcasecmp(item, "candybar")) { + cprintf("That hit the spot. You no longer feel\r\nhungry.\r\n"); + ateFood = 1; + return; + } + + /* Use bottle */ + if (!strcasecmp(item, "bottle")) { + if (currentLocation == Cistern) { + cprintf("You fill the bottle with water from the\r\ncistern and take a drink. You no longer\r\nfeel thirsty.\r\n"); + drankWater = 1; + return; + } else { + cprintf("The bottle is empty. If only you had\r\nsome water to fill it!\r\n"); + return; + } + } + + /* Use stale meat */ + if (!strcasecmp(item, "stale meat")) { + cprintf("The meat looked and tasted bad. You\r\nfeel very sick and pass out.\r\n"); + gameOver = 1; + return; + } + + /* Default */ + cprintf("Nothing happens\r\n"); +} + +/* Prompt user and get a line of input */ +void prompt() +{ + cprintf("? "); + gets(buffer); + + /* Remove trailing newline */ + // gb: Already removed + // buffer[strlen(buffer)-1] = '\0'; +} + +/* Do special things unrelated to command typed. */ +void doActions() +{ + if ((turnsPlayed == 10) && !lampLit) { + cprintf("It will be getting dark soon. You need\r\nsome kind of light or soon you won't\r\nbe able to see.\r\n"); + } + + if ((turnsPlayed >= 60) && (!lampLit || (!itemIsHere("lamp") && !carryingItem("lamp")))) { + cprintf("It is dark out and you have no light.\r\nYou stumble around for a while and\r\nthen fall, hit your head, and pass out.\r\n"); + gameOver = 1; + return; + } + + if ((turnsPlayed == 20) && !drankWater) { + cprintf("You are getting very thirsty.\r\nYou need to get a drink soon.\r\n"); + } + + if ((turnsPlayed == 30) && !ateFood) { + cprintf("You are getting very hungry.\r\nYou need to find something to eat.\r\n"); + } + + if ((turnsPlayed == 50) && !drankWater) { + cprintf("You pass out due to thirst.\r\n"); + gameOver = 1; + return; + } + + if ((turnsPlayed == 40) && !ateFood) { + cprintf("You pass out from hunger.\r\n"); + gameOver = 1; + return; + } + + if (currentLocation == Tunnel) { + if (itemIsHere("cheese")) { + cprintf("The rats go after the cheese.\r\n"); + } else { + if (ratAttack < 3) { + cprintf("The rats are coming towards you!\r\n"); + ++ratAttack; + } else { + cprintf("The rats attack and you pass out.\r\n"); + gameOver = 1; + return; + } + } + } + + /* wolfState values: 0 - wolf attacking 1 - wolf gone, Matthew in tree. 2 - Matthew safe, you won. Game over. */ + if (currentLocation == WolfTree) { + switch (wolfState) { + case 0: + cprintf("A wolf is circling around the tree.\r\nMatthew is up in the tree. You have to\r\nsave him! If only you had some kind of\r\nweapon!\r\n"); + break; + case 1: + cprintf("Matthew is afraid to come\r\ndown from the tree. If only you had\r\nsomething to coax him with.\r\n"); + break; + case 2: + cprintf("Congratulations! You succeeded and won\r\nthe game. I hope you had as much fun\r\nplaying the game as I did creating it.\r\n- Jeff Tranter <tranter@pobox.com>\r\n"); + gameOver = 1; + return; + break; + } + } +} + +/* Set variables to values for start of game */ +void initialize() +{ + currentLocation = Driveway1; + lampFilled = 0; + lampLit = 0; + ateFood = 0; + drankWater = 0; + ratAttack = 0; + wolfState = 0; + turnsPlayed = 0; + gameOver= 0; + + /* These doors can get changed during game and may need to be reset O*/ + Move[17][North] = 0; + Move[21][North] = 0; + + /* Set inventory to default */ + memset(Inventory, 0, sizeof(Inventory[0])*MAXITEMS); + Inventory[0] = Flashlight; + + /* Put items in their default locations */ + locationOfItem[0] = 0; /* NoItem */ + locationOfItem[1] = Driveway1; /* Key */ + locationOfItem[2] = Hayloft; /* Pitchfork */ + locationOfItem[3] = 0; /* Flashlight */ + locationOfItem[4] = WorkRoom; /* Lamp */ + locationOfItem[5] = Garage; /* Oil */ + locationOfItem[6] = Kitchen; /* Candybar */ + locationOfItem[7] = Driveway2; /* Bottle */ + locationOfItem[8] = GirlsBedroom; /* Doll */ + locationOfItem[9] = BoysBedroom; /* ToyCar */ + locationOfItem[10] = ServantsQuarters; /* Matches */ + locationOfItem[11] = Woods25; /* GoldCoin */ + locationOfItem[12] = Woods29; /* SilverCoin */ + locationOfItem[13] = DiningRoom; /* StaleMeat */ + locationOfItem[14] = DrawingRoom; /* Book */ + locationOfItem[15] = LaundryRoom; /* Cheese */ + locationOfItem[16] = MasterBedroom; /* OldRadio */ +} + +/* Main program (obviously) */ +int main(void) +{ + while (1) { + initialize(); + clearScreen(); + cprintf("%s", introText); + + while (!gameOver) { + prompt(); + if (buffer[0] == '\0') { + } else if (tolower(buffer[0]) == 'h') { + doHelp(); + } else if (tolower(buffer[0]) == 'i') { + doInventory(); + } else if ((tolower(buffer[0]) == 'g') + || !strcasecmp(buffer, "n") || !strcasecmp(buffer, "s") + || !strcasecmp(buffer, "e") || !strcasecmp(buffer, "w") + || !strcasecmp(buffer, "u") || !strcasecmp(buffer, "d") + || !strcasecmp(buffer, "north") || !strcasecmp(buffer, "south") + || !strcasecmp(buffer, "east") || !strcasecmp(buffer, "west") + || !strcasecmp(buffer, "up") || !strcasecmp(buffer, "down")) { + doGo(); + } else if (tolower(buffer[0]) == 'l') { + doLook(); + } else if (tolower(buffer[0]) == 't') { + doTake(); + } else if (tolower(buffer[0]) == 'e') { + doExamine(); + } else if (tolower(buffer[0]) == 'u') { + doUse(); + } else if (tolower(buffer[0]) == 'd') { + doDrop(); + } else if (tolower(buffer[0]) == 'q') { + doQuit(); + } else if (!strcasecmp(buffer, "xyzzy")) { + cprintf("Nice try, but that won't work here.\r\n"); + } else { + cprintf("I don't understand. Try 'help'.\r\n"); + } + + /* Handle special actions. */ + doActions(); + } + + cprintf("Game over after %d turns.\r\n", turnsPlayed); + cprintf("%s", "Do you want to play again (y/n)? "); + gets(buffer); + if (tolower(buffer[0]) == 'n') { + break; + } + } + return 0; +} diff --git a/contrib/c/eliza/Makefile b/contrib/c/eliza/Makefile @@ -0,0 +1,3 @@ +TARGET=eliza + +include ../../../sw/c/cc65_eris/Makefile.common diff --git a/contrib/c/eliza/eliza.c b/contrib/c/eliza/eliza.c @@ -0,0 +1,380 @@ +/* + * Original Source: + * https://github.com/mottosso/cs50x/blob/master/eliza.c + * with some minor changes by Gerd Beuster for CC65 + * + * eliza.c + * ys + * original code by Weizenbaum, 1966 + * this rendition based on Charles Hayden's Java implementation from http://chayden.net/eliza/Eliza.html + * + * Note: There are certainly far more optimal and elegant ways to code this... we kept this + * structure to be faithful to the original. -scaz + */ + +#include <ctype.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <conio.h> + +#define NUMKEYWORDS 37 +#define MAXLINELEN 80 +#define NUMSWAPS 14 + +const char *keywords[]= { + "CAN YOU","CAN I","YOU ARE","YOURE","I DONT","I FEEL", + "WHY DONT YOU","WHY CANT I","ARE YOU","I CANT","I AM","IM ", + "YOU ","I WANT","WHAT","HOW","WHO","WHERE", + "WHEN","WHY", + "NAME","CAUSE","SORRY","DREAM","HELLO","HI ","MAYBE", + " NO","YOUR","ALWAYS","THINK","ALIKE","YES","FRIEND", + "COMPUTER","CAR","NOKEYFOUND"}; + +const char *SWAPS[NUMSWAPS][2] = { + {"ARE","AM"}, + {"WERE", "WAS"}, + {"YOU","I"}, + {"YOUR", "MY"}, + {"IVE", "YOU'VE"}, + {"IM", "YOU'RE"}, + {"YOU", "ME"}, + {"ME", "YOU"}, + {"AM","ARE"}, + {"WAS", "WERE"}, + {"I","YOU"}, + {"MY", "YOUR"}, + {"YOUVE", "I'VE"}, + {"YOURE", "I'M"} +}; + +int ResponsesPerKeyword[NUMKEYWORDS]= { + 3,2,4,4,4,3, + 3,2,3,3,4,4, + 3,5,9,9,9,9, + 9,9, + 2,4,4,4,1,1,5, + 5,2,4,3,7,3,6, + 7,5,6}; + +const char *responses[NUMKEYWORDS][9] = { + { "DON'T YOU BELIEVE THAT I CAN*", + "PERHAPS YOU WOULD LIKE TO BE ABLE TO*", + "YOU WANT ME TO BE ABLE TO*"}, + { "PERHAPS YOU DON'T WANT TO*", + "DO YOU WANT TO BE ABLE TO*"}, + { "WHAT MAKES YOU THINK I AM*", + "DOES IT PLEASE YOU TO BELIEVE I AM*", + "PERHAPS YOU WOULD LIKE TO BE*", + "DO YOU SOMETIMES WISH YOU WERE*"}, + { "WHAT MAKES YOU THINK I AM*", + "DOES IT PLEASE YOU TO BELIEVE I AM*", + "PERHAPS YOU WOULD LIKE TO BE*", + "DO YOU SOMETIMES WISH YOU WERE*"}, + { "DON'T YOU REALLY*", + "WHY DON'T YOU*", + "DO YOU WISH TO BE ABLE TO*", + "DOES THAT TROUBLE YOU?"}, + { "TELL ME MORE ABOUT SUCH FEELINGS.", + "DO YOU OFTEN FEEL*", + "DO YOU ENJOY FEELING*"}, + { "DO YOU REALLY BELIEVE I DON'T*", + "PERHAPS IN GOOD TIME I WILL*", + "DO YOU WANT ME TO*"}, + { "DO YOU THINK YOU SHOULD BE ABLE TO*", + "WHY CAN'T YOU*"}, + { "WHY ARE YOU INTERESTED IN WHETHER OR NOT I AM*", + "WOULD YOU PREFER IF I WERE NOT*", + "PERHAPS IN YOUR FANTASIES I AM*"}, + { "HOW DO YOU KNOW YOU CAN'T*", + "HAVE YOU TRIED?", + "PERHAPS YOU CAN NOW*"}, + { "DID YOU COME TO ME BECAUSE YOU ARE*", + "HOW LONG HAVE YOU BEEN*", + "DO YOU BELIEVE IT IS NORMAL TO BE*", + "DO YOU ENJOY BEING*"}, + { "DID YOU COME TO ME BECAUSE YOU ARE*", + "HOW LONG HAVE YOU BEEN*", + "DO YOU BELIEVE IT IS NORMAL TO BE*", + "DO YOU ENJOY BEING*"}, + { "WE WERE DISCUSSING YOU-- NOT ME.", + "OH, I*", + "YOU'RE NOT REALLY TALKING ABOUT ME, ARE YOU?"}, + { "WHAT WOULD IT MEAN TO YOU IF YOU GOT*", + "WHY DO YOU WANT*", + "SUPPOSE YOU SOON GOT*", + "WHAT IF YOU NEVER GOT*", + "I SOMETIMES ALSO WANT*"}, + { "WHY DO YOU ASK?", + "DOES THAT QUESTION INTEREST YOU?", + "WHAT ANSWER WOULD PLEASE YOU THE MOST?", + "WHAT DO YOU THINK?", + "ARE SUCH QUESTIONS ON YOUR MIND OFTEN?", + "WHAT IS IT THAT YOU REALLY WANT TO KNOW?", + "HAVE YOU ASKED ANYONE ELSE?", + "HAVE YOU ASKED SUCH QUESTIONS BEFORE?", + "WHAT ELSE COMES TO MIND WHEN YOU ASK THAT?"}, + { "WHY DO YOU ASK?", + "DOES THAT QUESTION INTEREST YOU?", + "WHAT ANSWER WOULD PLEASE YOU THE MOST?", + "WHAT DO YOU THINK?", + "ARE SUCH QUESTIONS ON YOUR MIND OFTEN?", + "WHAT IS IT THAT YOU REALLY WANT TO KNOW?", + "HAVE YOU ASKED ANYONE ELSE?", + "HAVE YOU ASKED SUCH QUESTIONS BEFORE?", + "WHAT ELSE COMES TO MIND WHEN YOU ASK THAT?"}, + { "WHY DO YOU ASK?", + "DOES THAT QUESTION INTEREST YOU?", + "WHAT ANSWER WOULD PLEASE YOU THE MOST?", + "WHAT DO YOU THINK?", + "ARE SUCH QUESTIONS ON YOUR MIND OFTEN?", + "WHAT IS IT THAT YOU REALLY WANT TO KNOW?", + "HAVE YOU ASKED ANYONE ELSE?", + "HAVE YOU ASKED SUCH QUESTIONS BEFORE?", + "WHAT ELSE COMES TO MIND WHEN YOU ASK THAT?"}, + { "WHY DO YOU ASK?", + "DOES THAT QUESTION INTEREST YOU?", + "WHAT ANSWER WOULD PLEASE YOU THE MOST?", + "WHAT DO YOU THINK?", + "ARE SUCH QUESTIONS ON YOUR MIND OFTEN?", + "WHAT IS IT THAT YOU REALLY WANT TO KNOW?", + "HAVE YOU ASKED ANYONE ELSE?", + "HAVE YOU ASKED SUCH QUESTIONS BEFORE?", + "WHAT ELSE COMES TO MIND WHEN YOU ASK THAT?"}, + { "WHY DO YOU ASK?", + "DOES THAT QUESTION INTEREST YOU?", + "WHAT ANSWER WOULD PLEASE YOU THE MOST?", + "WHAT DO YOU THINK?", + "ARE SUCH QUESTIONS ON YOUR MIND OFTEN?", + "WHAT IS IT THAT YOU REALLY WANT TO KNOW?", + "HAVE YOU ASKED ANYONE ELSE?", + "HAVE YOU ASKED SUCH QUESTIONS BEFORE?", + "WHAT ELSE COMES TO MIND WHEN YOU ASK THAT?"}, + { "WHY DO YOU ASK?", + "DOES THAT QUESTION INTEREST YOU?", + "WHAT ANSWER WOULD PLEASE YOU THE MOST?", + "WHAT DO YOU THINK?", + "ARE SUCH QUESTIONS ON YOUR MIND OFTEN?", + "WHAT IS IT THAT YOU REALLY WANT TO KNOW?", + "HAVE YOU ASKED ANYONE ELSE?", + "HAVE YOU ASKED SUCH QUESTIONS BEFORE?", + "WHAT ELSE COMES TO MIND WHEN YOU ASK THAT?"}, + { "NAMES DON'T INTEREST ME.", + "I DON'T CARE ABOUT NAMES-- PLEASE GO ON."}, + { "IS THAT THE REAL REASON?", + "DON'T ANY OTHER REASONS COME TO MIND?", + "DOES THAT REASON EXPLAIN ANY THING ELSE?", + "WHAT OTHER REASONS MIGHT THERE BE?"}, + { "PLEASE DON'T APOLOGIZE.", + "APOLOGIES ARE NOT NECESSARY.", + "WHAT FEELINGS DO YOU HAVE WHEN YOU APOLOGIZE?", + "DON'T BE SO DEFENSIVE!"}, + { "WHAT DOES THAT DREAM SUGGEST TO YOU?", + "DO YOU DREAM OFTEN?", + "WHAT PERSONS APPEAR IN YOUR DREAMS?", + "ARE YOU DISTURBED BY YOUR DREAMS?"}, + { "HOW DO YOU DO--PLEASE STATE YOUR PROBLEM."}, + { "HOW DO YOU DO--PLEASE STATE YOUR PROBLEM."}, + { "YOU DON'T SEEM QUITE CERTAIN.", + "WHY THE UNCERTAIN TONE?", + "CAN'T YOU BE MORE POSITIVE?", + "YOU AREN'T SURE?", + "DON'T YOU KNOW?"}, + { "ARE YOU SAYING NO JUST TO BE NEGATIVE?", + "YOU ARE BEING A BIT NEGATIVE.", + "WHY NOT?", + "ARE YOU SURE?", + "WHY NO?"}, + { "WHY ARE YOU CONCERNED ABOUT MY*", + "WHAT ABOUT YOUR OWN*"}, + { "CAN YOU THINK OF A SPECIFIC EXAMPLE?", + "WHEN?", + "WHAT ARE YOU THINKING OF?", + "REALLY, ALWAYS?"}, + { "DO YOU REALLY THINK SO?", + "BUT YOU ARE NOT SURE YOU*", + "DO YOU DOUBT YOU*"}, + { "IN WHAT WAY?", + "WHAT RESEMBLANCE DO YOU SEE?", + "WHAT DOES THE SIMILARITY SUGGEST TO YOU?", + "WHAT OTHER CONNECTIONS DO YOU SEE?", + "COULD THERE REALLY BE SOME CONNECTION?", + "HOW?"}, + { "YOU SEEM QUITE POSITIVE.", + "ARE YOU SURE?", + "I SEE.", + "I UNDERSTAND."}, + { "WHY DO YOU BRING UP THE TOPIC OF FRIENDS?", + "DO YOUR FRIENDS WORRY YOU?", + "DO YOUR FRIENDS PICK ON YOU?", + "ARE YOU SURE YOU HAVE ANY FRIENDS?", + "DO YOU IMPOSE ON YOUR FRIENDS?", + "PERHAPS YOUR LOVE FOR FRIENDS WORRIES YOU?"}, + { "DO COMPUTERS WORRY YOU?", + "ARE YOU TALKING ABOUT ME IN PARTICULAR?", + "ARE YOU FRIGHTENED BY MACHINES?", + "WHY DO YOU MENTION COMPUTERS?", + "WHAT DO YOU THINK MACHINES HAVE TO DO WITH YOUR PROBLEM?", + "DON'T YOU THINK COMPUTERS CAN HELP PEOPLE?", + "WHAT IS IT ABOUT MACHINES THAT WORRIES YOU?"}, + { "OH, DO YOU LIKE CARS?", + "MY FAVORITE CAR IS A LAMBORGINI COUNTACH. WHAT IS YOUR FAVORITE CAR?", + "MY FAVORITE CAR COMPANY IS FERRARI. WHAT IS YOURS?", + "DO YOU LIKE PORSCHES?", + "DO YOU LIKE PORSCHE TURBO CARRERAS?"}, + { "SAY, DO YOU HAVE ANY PSYCHOLOGICAL PROBLEMS?", + "WHAT DOES THAT SUGGEST TO YOU?", + "I SEE.", + "I'M NOT SURE I UNDERSTAND YOU FULLY.", + "COME, COME ELUCIDATE YOUR THOUGHTS.", + "CAN YOU ELABORATE ON THAT?", + "THAT IS QUITE INTERESTING."} + + +}; + +void print_center(const char *msg) { + int numspaces=(MAXLINELEN-strlen(msg))/2; + int i; + + for(i=0;i<numspaces;i++) + cprintf(" "); + cprintf("%s\r\n", msg); + return; +} + +void print_title () { + cprintf("\r\n\r\n"); + print_center("*** ELIZA ***"); + print_center("Original code by Weizenbaum, 1966"); + print_center("This is Marcus Ottosson's version from 2016"); + print_center("To stop Eliza, type 'bye'"); + cprintf("\r\n\r\n"); + cprintf("HI! I'M ELIZA. WHAT'S YOUR PROBLEM?\r\n"); +} + +void readline(char *instr) { + char c; + int slen=0; + + c=cgetc(); + cputc(c); + while (c != '\r') + { + // removes punctuation and sets to uppercase + if(isalpha(c) || isspace(c)) + instr[slen++]=toupper(c); + if(slen>MAXLINELEN-1) + { + cprintf("Exceeded Max Line Length\r\n"); + exit(0); + } + c=cgetc(); + cputc(c); + } + cputc('\n'); + instr[slen]='\0'; +} + + +// Number of local variables of CC65 limited, +// therefore we make all variables of main +// global. +int k,baseLength; +int whichReply[NUMKEYWORDS]; +char lastinput[MAXLINELEN]; +char reply[MAXLINELEN]; +char *baseResponse, *token; +const char separator[2]=" "; +char inputstr[MAXLINELEN]; +int x; +char *location; +int s; +void main(void) +{ + + // use the first reply for each keyword match the first time you see that keyword + for (x=0;x< NUMKEYWORDS; x++) { + whichReply[x] = 0; + } + + // print a nice centered title screen + print_title(); + + lastinput[0]='\0'; + + while (1) { + readline(inputstr); + + // check for termination + if (strcmp(inputstr,"BYE")==0) + break; + + // check for repeated entries + if (strcmp(lastinput,inputstr)==0) + { + cprintf("PLEASE DON'T REPEAT YOURSELF!\r\n"); + continue; + } + strncpy(lastinput,inputstr,strlen(inputstr)+1); + + // see if any of the keywords is contained in the input + // if not, we use the last element of keywords as our default responses + strcpy(reply,""); + for(k=0;k<NUMKEYWORDS-1;k++) + { + location=strstr(inputstr, keywords[k]); + if(location != NULL) + break; + } + + // Build Eliza's response + // start with Eliza's canned response, based on the keyword match + baseResponse = (char *) responses[k][whichReply[k]]; + baseLength = strlen(baseResponse); + + if(baseResponse[baseLength-1] != '*') + { + // if we have a baseResponse without an asterix, just use it as-is + strcat(reply, baseResponse); + } + else + { + // if we do have an asterix, fill in the remaining with the user input + // use all but the last character of the base response + strncat(reply, baseResponse, baseLength-1); + + // now add in the rest of the user's input, starting at <location> + // but skip over the keyword itself + location+=strlen(keywords[k]); + // take them one word at a time, so that we can substitute pronouns + token = strtok(location, separator); + while(token != NULL) + { + for(s=0;s<NUMSWAPS;s++) + { + if(strcmp(SWAPS[s][0], token) == 0) + { + token = (char *) SWAPS[s][1]; + break; + } + } + strcat(reply," "); + strcat(reply, token); + token=strtok(NULL, separator); + }; + strcat(reply, "?"); + } + cprintf("%s\r\n", reply); + + // next time, use the next appropriate reply for that keyword + whichReply[k]++; + if ( whichReply[k] >= ResponsesPerKeyword[k]) + whichReply[k] = 0; + + } + + cprintf( "GOODBYE! THANKS FOR VISITING WITH ME...\r\n"); + cgetc(); + +} diff --git a/contrib/c/mmind/Makefile b/contrib/c/mmind/Makefile @@ -0,0 +1,3 @@ +TARGET=mmind + +include ../../../sw/c/cc65_eris/Makefile.common diff --git a/contrib/c/mmind/mmind.c b/contrib/c/mmind/mmind.c @@ -0,0 +1,115 @@ +/* simple text mode mastermind game for lccwin-32 + compiled also without any changes under cc65 6502 cross compiler (www.cc65.org) + (c) 2002-2006 Peter Sieg, Rabishauerstr.9, D-37603 Holzminden, peter.sieg1@gmx.de + program under GNU GPL + greeting to my family: Heike, Robin and Janis + + Source: ftp://www.musoftware.de/pub/uz/cc65/contrib/mmind-1.0.0.zip + + 2021: Minor changes by Gerd Beuster (gb) to get it run on Eris 2010 +*/ + +#include <conio.h> +#include <string.h> +#include <stdarg.h> +#include <time.h> +#include <stdlib.h> + +#define WORD int + + +// gb: Changed I/O from stdio to conio. + +/*--------------------------------------------------------------------------- +// Main programm +//-------------------------------------------------------------------------*/ + +// gb: Not available in conio, therefore added. +void gets(char *s) { + char c; + while ((c = cgetc()) != '\r') { + cputc(c); + *s = c; + s++; + } + *s = '\0'; + cputs("\r\n"); +}; + +void main() +{ + unsigned ret; + WORD a; + WORD statusm,apid; + char ad; + int bv; + char ch[20],my[20]; + WORD done; + WORD i,j,count,offcnt; + WORD z1,z2,z3,z4; /* 4 digits for creation of my 4-digit char array */ + WORD echo_xy[4]; + unsigned int seed; + + count = 1; offcnt = 0; done = 0; + /* create random 4-digit string in char array my */ + /* strcpy(my,"1234"); */ + + // gb: Clock not available, therefore we measure the + // time until the user presses a key. + // srand((unsigned int)time(NULL)); + cputs("\r\nPress a key.\r\n"); + while(!kbhit()){ + ++seed; + } + cgetc(); + srand(seed); + + z1 = (rand() % 9) + 1; /* 1..9 */ + do { z2 = (rand() % 9) + 1; } while (z2==z1); + do { z3 = (rand() % 9) + 1; } while ((z3==z1) || (z3==z2)); + do { z4 = (rand() % 9) + 1; } while ((z4==z1) || (z4==z2) || (z4==z3)); + my[0] = z1+48; /* int 1..9 + ascii(48)='0' => '1'..'9' */ + my[1] = z2+48; + my[2] = z3+48; + my[3] = z4+48; + my[4] = 0; /* terminate string */ + + // cprintf("Guess my 4-digit number...(%s); Use 0 to abort.\r\n","****"); /* insert my to see code */ + cprintf("Guess my 4-digit number...(%s); Use 0 to abort.\r\n",my); /* insert my to see code */ + // gb: Additional explanation + cputs("Hint:\r\n"); + cputs("+ for each correct digit\r\n"); + cputs("# for each correct digit in the correct position\r\n"); + do { + + cprintf("\r\nGuess %d:",count+offcnt*10); + gets(ch); + ch[4] = 0; + // gb: Cancel condition missing from original version. + if ((ch[0] == '0') && (ch[1] == '\0')) { + break; + } + if (strcmp(my,ch)==0) done = 1; /* success; code broken */ + /* check how near guess it and print hint */ + /* for right digit at right position print # */ + /* for right digit but wrong position print + */ + if (done==0) { + for (i=0;i<4;i++) { + if (ch[i]==my[i]) cprintf("# "); + else for (j=0;j<4;j++) if (ch[i]==my[j]) cprintf("+ "); + } + } + count++; + if (count==11) { + count = 1; + offcnt++; + } + } while (done==0); + if (done==1) + cprintf("\r\n\r\nYou broke the code!"); + else + cprintf("\r\n\r\nYou gave up.."); + cprintf("\r\nEnter any chars and press return to exit.."); + gets(ch); +} + diff --git a/contrib/c/mmind/mmind.txt b/contrib/c/mmind/mmind.txt @@ -0,0 +1,8 @@ +mmind-1.0.2 - A simple C mastermind game. +(c) peter.sieg1@gmx.de - released under GNU GPL version 2 or higher. +Compiled with: cc65 version 2.11.0 +Tested under WinVICE 1.20 and real C64 hardware. + +You have to guess a 4 digit number. All digits are different, no zero. +As a hint, the program will print + for a right digit, but wrong position +and # for right digit and right position. diff --git a/contrib/c/wumpus/Makefile b/contrib/c/wumpus/Makefile @@ -0,0 +1,3 @@ +TARGET=wumpus + +include ../../../sw/c/cc65_eris/Makefile.common diff --git a/contrib/c/wumpus/wumpus.c b/contrib/c/wumpus/wumpus.c @@ -0,0 +1,381 @@ +// Copyright Creative Computing, Morristown, New Jersey +// C conversion by Payton Byrd from BASIC in "More BASIC +// Computer Games" by Creative Computing. +// +// Adapted for CC65 by gb + +#include <ctype.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdbool.h> +#include <time.h> +#include <conio.h> + +unsigned char arrows = 5u; + +unsigned char items[6]; + +// Rooms and their connections +const unsigned char rooms[20][3] = +{ + { 2, 5, 8 }, // 1 + { 1, 3, 10 }, // 2 + { 2, 4, 12 }, // 3 + { 3, 5, 14 }, // 4 + { 1, 4, 6 }, // 5 + { 5, 7, 15 }, // 6 + { 6, 8, 17 }, // 7 + { 1, 7, 9 }, // 8 + { 8, 10, 18 }, // 9 + { 2, 9, 11 }, // 10 + { 10, 12, 19 }, // 11 + { 3, 11, 13 }, // 12 + { 12, 14, 20 }, // 13 + { 4, 13, 15 }, // 14 + { 6, 14, 16 }, // 15 + { 15, 17, 20 }, // 16 + { 7, 16, 18 }, // 17 + { 9, 17, 19 }, // 18 + { 11, 18, 20 }, // 19 + { 13, 16, 19 } // 20 +}; + +// Generates a random number between 1 and 20 +unsigned char fna() +{ + return (unsigned char)(rand() % 20u + 1u); +} + +// Generates a random number between 1 and 3 +unsigned char fnb() +{ + return (unsigned char)(rand() % 3u + 1u); +} + +// Generates a random number between 1 and 4 +unsigned char fnc() +{ + return (unsigned char)(rand() % 4u + 1u); +} + +// Ensures all items are in different rooms. +bool validateItems(void) +{ + unsigned char i, j; + + for(i = 0; i < 6; ++i) + { + for(j = 0; j < 6; ++j) + { + if(i != j) + { + if(items[i] == items[j]) + { + // OOPS, two items in + // same room, no good! + return false; + } + } + } + } + + return true; +} + +// Place each item in a separate room. +void randomizeItems(unsigned int seed) +{ + // Seeding of RNG changed by gb + srand(seed); + + do + { + items[0] = fna(); // You + items[1] = fna(); // Wumpus + items[2] = fna(); // Bat + items[3] = fna(); // Bat + items[4] = fna(); // Pit + items[5] = fna(); // Pit + } + while(!validateItems()); +} + +// Displays information about the current room. +void printLocation() +{ + unsigned char k; + + cputs("\r\n"); + + for(k = 0; k < 3; ++k) + { + if(rooms[items[0]-1][k] == items[1]) + { + cputs("I smell a wumpus!\r\n"); + } + + if(rooms[items[0]-1][k] == items[2] + || rooms[items[0]-1][k] == items[3]) + { + cputs("Bats nearby!\r\n"); + } + + if(rooms[items[0]-1][k] == items[4] + || rooms[items[0]-1][k] == items[5]) + { + cputs("I feel a draft!\r\n"); + } + } + + cprintf("You are in room %u\r\n", items[0]); + cprintf("Tunnels lead to %u, %u, %u\r\n", + rooms[items[0]-1][0], + rooms[items[0]-1][1], + rooms[items[0]-1][2]); +} + +// User selects whether to shoot or move. +unsigned char chooseOption() +{ + unsigned char input = '\0'; + + while(input != 'm' && input != 's' && input != 'q') + { + cprintf("\r\nShoot or move [M or S]: "); + input = cgetc(); + tolower(input); + } + + return input; +} + +// User selects the number of rooms +// to shoot the arrow through. +unsigned char chooseNumberOfRooms() +{ + unsigned char input = 0; + unsigned char result[1] = ""; + + while(input < '1' || input > '5') + { + cprintf("\r\nNo. of rooms (1-5):"); + input = cgetc(); + } + + sprintf(result, "%c", input); + + return (unsigned char)atoi(result); +} + +// User selects a room adjacent to the current +// room. +unsigned char chooseRoom(unsigned char currentRoom) +{ + char input[3]; + char selectedRoom; + int result = -1; + + do + { + cprintf("\r\nChoose room [%u, %u, %u]: ", + rooms[currentRoom - 1][0], + rooms[currentRoom - 1][1], + rooms[currentRoom - 1][2]); + + cscanf("%s", input); + + selectedRoom = (unsigned char)atoi(input); + + if(selectedRoom == rooms[currentRoom - 1][0] + || selectedRoom == rooms[currentRoom - 1][1] + || selectedRoom == rooms[currentRoom - 1][2]) + { + result = selectedRoom; + } + } + while(result == -1); + + cputs("\r\n"); + + return (unsigned char)result; +} + +// User fires an arrow +char shootArrow() +{ + unsigned char counter, room; + unsigned char numberOfRooms; + + // Get the number of rooms for the arrow + // to travel. + numberOfRooms = chooseNumberOfRooms(); + + // Start in our room. + room = items[0]; + + // Loop the number of rooms selected. + for(counter = 0; counter < numberOfRooms; ++counter) + { + // Move arrow to chosen room. + room = chooseRoom(room); + } + + // Did we hit him? + if(room == items[1]) + { + cputs("\r\nAHA! You got the Wumpus!\r\n"); + return 1; + } + + // Did we hit ourself? + if(room == items[0]) + { + cputs("\r\nOUCH! The arrow got you!\r\n"); + return -1; + } + + // Decrement arrow count + --arrows; + cputs("\r\nMissed.\r\n"); + + if(arrows == 0) + { + // Uh oh. + cputs("\r\nYou are out of arrows!\r\n"); + return -1; + } + + return 0; +} + +// Moves the wumpus. +char moveWumpus() +{ + // Randomly chooses another room. + items[1] = rooms[items[1]][fnc()]; + + // Are we sharing a room with the wumpus now? + if(items[1] == items[0]) + { + cputs("\r\n\r\nTSK TSK TSK - Wumpus got you!"); + return 1; + } + + return 0; +} + +// Move the player +char movePlayer() +{ + unsigned char newRoom = 0; + char wumpusResult; + + // Choose the room to move to. + newRoom = chooseRoom(items[0]); + + // Did we actually move? + if(items[0] == newRoom) + { + cputs("Can't stay still!"); + return 0; + } + + // Set the items array to our new + // room. + items[0] = newRoom; + + // Did we find the wumpus? + if(items[1] == newRoom) + { + // Yup, make him move. + cputs("\r\n\r\n... OOPS! Bumped a Wumpus!\r\n"); + + wumpusResult = moveWumpus(); + + return wumpusResult; + } + // Did we find a bat? + else if(items[2] == newRoom || items[3] == newRoom) + { + // Yup, it's off to Elsewhereville with the player + cputs("\r\n\r\nZAP--Super Bat Snatch!! Elsewhereville for you!\r\n"); + + // Player's position is randomly selected from + // all 20 rooms. + items[0] = fna(); + } + // Did we find a pit? + else if(items[4] == newRoom || items[5] == newRoom) + { + // Dum, dum, de dum... + cputs("\r\n\r\nYYYIIIIEEEE . . . Fell in pit!\r\n"); + return 1; + } + + return 0; +} + +// Ye old main routine. +void main(void) +{ + unsigned char option, shootResult; + unsigned int seed; + + // Polite header. +#ifdef __CBM__ + putchar(147); +#endif + cputs("\r\nPress a key.\r\n"); + while(!kbhit()){ + ++seed; + } + cgetc(); + + cputs(" WUMPUS!\r\n"); + cputs(" CREATIVE COMPUTING\r\n"); + cputs(" Morristown, NJ\r\n"); + cputs(" Converted to C by Payton Byrd\r\n"); + + + // Put everything in it's place. + randomizeItems(seed); + + // Loop until someone gets hurt. + for(;;) + { + cputs("\r\n"); + + // Display the location to the user. + printLocation(); + + // Get their choice of move or shoot + option = tolower(chooseOption()); + if(option == 'q') + break; + + if(option == 's') + { + // They chose to shoot. Get result. + shootResult = shootArrow(); + + if(shootResult != 0) + { + // Something got shot + break; + } + } + else + { + // They chose to move. Get result. + if(movePlayer() == 1) + { + // Something bad happened to the player. + break; + } + } + } + // That's all, folks! + cputs("\r\nThanks for playing!\r\n"); + cgetc(); +} diff --git a/contrib/write_default_files.sh b/contrib/write_default_files.sh @@ -1,10 +1,15 @@ #!/bin/bash + +# This scripts writes a selection of applications both from the main +# distribution and the contrib directory to an SD card. + KFS_CMD="sudo ../tools/kfs.py" DEVICE=$1 -SW_MAIN=../sw -SW_CONTRIB=. +SW_MAIN=../sw/asm +SW_CONTRIB=./asm +SW_CC65=./c -for i in 10print ansi_test serial_line_echo stack_test ttt via_test interrupts +for i in 10print serial_line_echo ttt via_test do pushd ${SW_MAIN}/$i make clean all @@ -20,14 +25,22 @@ do popd done +for i in eliza mmind wumpus +do + pushd ${SW_CC65}/$i + make clean all + popd +done + # ${KFS_CMD} ${DEVICE} format -${KFS_CMD} ${DEVICE} store 0 "10print" ${SW_MAIN}/10print/10print.bin -${KFS_CMD} ${DEVICE} store 1 "Tic Tac Toe" ${SW_MAIN}/ttt/ttt.bin -${KFS_CMD} ${DEVICE} store 2 "Tic Tac Toe (Computer vs. Computer)" ${SW_MAIN}/ttt/ttt_test.bin +${KFS_CMD} ${DEVICE} store 0 "Tic Tac Toe" ${SW_MAIN}/ttt/ttt.bin +${KFS_CMD} ${DEVICE} store 1 "Tic Tac Toe (Computer vs. Computer)" ${SW_MAIN}/ttt/ttt_test.bin +${KFS_CMD} ${DEVICE} store 2 "10PRINT" ${SW_MAIN}/10print/10print.bin ${KFS_CMD} ${DEVICE} store 3 "Microchess" ${SW_CONTRIB}/microchess/microchess.bin -${KFS_CMD} ${DEVICE} store 4 "Tiny-BASIC" ${SW_CONTRIB}/Tiny-BASIC/tinybasic.bin -${KFS_CMD} ${DEVICE} store 5 "Wozmon" ${SW_CONTRIB}/wozmon/wozmon.bin -${KFS_CMD} ${DEVICE} store 6 "Ansi Test" ${SW_MAIN}/ansi_test/ansi_test.bin -${KFS_CMD} ${DEVICE} store 7 "Stack Test" ${SW_MAIN}/stack_test/stack_test.bin -${KFS_CMD} ${DEVICE} store 8 "Interrupts Test" ${SW_MAIN}/interrupts/interrupts.bin +${KFS_CMD} ${DEVICE} store 4 "Eliza" ${SW_CC65}/eliza/eliza.bin +${KFS_CMD} ${DEVICE} store 5 "Wumpus" ${SW_CC65}/wumpus/wumpus.bin +${KFS_CMD} ${DEVICE} store 6 "Master Mind" ${SW_CC65}/mmind/mmind.bin +${KFS_CMD} ${DEVICE} store 7 "The Abandoned Farmhouse Adventure" ${SW_CC65}/abandoned_farmhouse/adventure.bin +${KFS_CMD} ${DEVICE} store 8 "Tiny-BASIC" ${SW_CONTRIB}/Tiny-BASIC/tinybasic.bin +${KFS_CMD} ${DEVICE} store 9 "Wozmon" ${SW_CONTRIB}/wozmon/wozmon.bin ${KFS_CMD} ${DEVICE} ls diff --git a/hw/bus_logic/Makefile b/hw/bus_logic/Makefile @@ -9,4 +9,4 @@ clean: rm -f ${TARGET}.chp ${TARGET}.fus ${TARGET}.pin ${TARGET}.jed flash: ${TARGET}.jed - sudo ~/opt/minipro-0.5/minipro -p ATF16V8B -w $< + sudo ~/opt/minipro-0.5/minipro -p ATF16V8CZ -w $< diff --git a/roms/README.md b/roms/README.md @@ -1,5 +1,5 @@ # os/ This is the main "operating system" ROM. It provides some library -functions and most notably a mechanism to load software from the -serial interface into the RAM and execute it. +functions and most notably mechanisms to load software from the serial +interface or the SD card into RAM and execute it. diff --git a/roms/os/ds.asm b/roms/os/ds.asm @@ -10,7 +10,7 @@ ;;; Software implementation of data stack. In difference to typical ;;; stack implementations, this stack grows from bottom to top, because -;;; we want to access eleements using indirected indexed access with +;;; we want to access elements using indirected indexed access with ;;; the stack frame pointer as base. Therefore the stack should ;;; be initialized with the first unoccupied RAM address after the ;;; program code. @@ -31,7 +31,7 @@ ;;; modes are supported: immediate, zero-page, and local variables. ;;; For immediate and zero page, the third parameter must be lda. ;;; For local variables, the third parameter must be lda_LOCAL. -;;; See sw/stack_test/stack_test.asm for examles. +;;; See sw/asm/stack_test/stack_test.asm for examles. ds .namespace diff --git a/roms/os/io.asm b/roms/os/io.asm @@ -97,6 +97,9 @@ putc: ;; a manual delay. sta io.acia_data_reg .switch CLOCK_SPEED +.case 8 ; 8 Mhz Clock + SERIAL_SEND_DELAY_X = $c3 + SERIAL_SEND_DELAY_Y = $04 .case 4 ; 4 Mhz Clock SERIAL_SEND_DELAY_X = $c3 SERIAL_SEND_DELAY_Y = $02 diff --git a/roms/os/os.asm b/roms/os/os.asm @@ -50,6 +50,8 @@ boot: .block jsr via.init #io.SETUP cli + ldx #$ff ; Reset return stack + txs #ds.INIT_STACK os.ram_end-$206+$1 ;Minimal stack size to access SD card #io.PRINTSNL "***** Eris 2010 8-Bit System (" .. VERSION .. ") *****" jsr check_for_program_download @@ -147,6 +149,10 @@ transmit_block: jsr io.putc lda xor_checksum jsr io.putc + ;; Application number is passed to program + ;; in A. Since uploaded programs do not have + ;; an application number, we load A with $ff + lda #$ff ;; Start program jmp $0200 .bend @@ -228,9 +234,9 @@ derefer_ram_irq: jmp (os.irq_vector) default_program: -;; .include "../../sw/10print/10print.asm" -;; .include "../../sw/ttt/ttt.asm" -.include "../../sw/load_from_card/load_from_card.asm" +;; .include "../../sw/asm/10print/10print.asm" +;; .include "../../sw/asm/ttt/ttt.asm" +.include "../../sw/asm/load_from_card/load_from_card.asm" ;;; Vectors .fill $fffa-*, $ff diff --git a/roms/os/sd.asm b/roms/os/sd.asm @@ -69,6 +69,20 @@ read_block: .endif lda #$51 ; CMD17 (READ_SINGLE_BLOCK) jsr via.spi_set +.if DEBUG + ldy #$00 + lda (ds.frame_ptr),y + jsr io.puth + ldy #$01 + lda (ds.frame_ptr),y + jsr io.puth + ldy #$02 + lda (ds.frame_ptr),y + jsr io.puth + ldy #$03 + lda (ds.frame_ptr),y + jsr io.puth +.endif jsr send_block_number lda #$01 ; Dummy CRC jsr via.spi_set @@ -108,9 +122,9 @@ loop: dex bne loop #mem.SUB_WORD sd.data, $0200 ; Restore pointer - clc #sd.RECEIVE $2 ; Get CRC jsr ds.delete_stack_frame + clc rts read_failed: #io.PRINTSNL "Error reading SD card!" diff --git a/roms/os/via.asm b/roms/os/via.asm @@ -116,12 +116,38 @@ cont: bne read_bits_loop #mem.CLEAR_BITS via.ra, #(MOSI|SCK) ; Set MOSI and SCK low on completion. lda via.spi_buffer +.if DEBUG + phx + phy + pha + #io.PRINTS "< " + pla + pha + jsr io.puth + #io.PRINTNL + pla + ply + plx +.endif rts .bend ;;; Write byte via SPI spi_set: .block +.if DEBUG + phx + phy + pha + #io.PRINTS "> " + pla + pha + jsr io.puth + #io.PRINTNL + pla + ply + plx +.endif sta via.spi_buffer ; We send from spi_buffer ldx #$08 ; 8 bit to write write_bits_loop: diff --git a/sw/Makefile.common b/sw/Makefile.common @@ -1,16 +0,0 @@ -CFLAGS=-C --line-numbers --tab-size=1 -Wall -c -b -I../../roms/os/ -VERSION="$(shell git describe --tags)" - -all: $(TARGET).bin $(TARGET)_symon.bin - -%.bin: %.asm - 64tass $(CFLAGS) $(DEFS) -DVERSION="'$(VERSION)'" -DSYMON=false -l $(TARGET).l -L $(TARGET).lst "$<" -o "$@" - -%_symon.bin: %.asm - 64tass $(CFLAGS) $(DEFS) -DVERSION="'$(VERSION)'" -DSYMON=true -l $(TARGET)_symon.l -L $(TARGET)_symon.lst "$<" -o "$@" - -upload: $(TARGET).bin - ../../tools/boot.py $(TARGET).bin - -clean: - rm -f *.bin *.l *.lst diff --git a/sw/README.md b/sw/README.md @@ -1,34 +0,0 @@ -# load_from_card/ - -Reads directory and prints it on serial line. The user can choose a -program to run. This is the default program included by -roms/boot/boot.bin. Refer to this program as an example on how to -access the SD card and how to use the data stack. - -# ttt/ - -Tic-Tac-Toe - -# serial_line_echo/ - -Test serial input/output. - -# 10print/ - -https://10print.org/ - -# interrupts/ - -Testing interrupt handling. - -# stack_test/ - -Testing the data stack. - -# via_test/ - -Testing VIA GPIO. - -# ansi_term/ - -Testing ANSI term commands. diff --git a/sw/10print/10print.asm b/sw/asm/10print/10print.asm diff --git a/sw/10print/Makefile b/sw/asm/10print/Makefile diff --git a/sw/asm/Makefile.common b/sw/asm/Makefile.common @@ -0,0 +1,16 @@ +CFLAGS=-C --line-numbers --tab-size=1 -Wall -c -b -I../../../roms/os/ +VERSION="$(shell git describe --tags)" + +all: $(TARGET).bin $(TARGET)_symon.bin + +%.bin: %.asm + 64tass $(CFLAGS) $(DEFS) -DVERSION="'$(VERSION)'" -DSYMON=false -l $(TARGET).l -L $(TARGET).lst "$<" -o "$@" + +%_symon.bin: %.asm + 64tass $(CFLAGS) $(DEFS) -DVERSION="'$(VERSION)'" -DSYMON=true -l $(TARGET)_symon.l -L $(TARGET)_symon.lst "$<" -o "$@" + +upload: $(TARGET).bin + ../../../tools/boot.py $(TARGET).bin + +clean: + rm -f *.bin *.l *.lst diff --git a/sw/asm/README.md b/sw/asm/README.md @@ -0,0 +1,34 @@ +# load_from_card/ + +Reads directory and prints it on serial line. The user can choose a +program to run. This is the default program included by +roms/os/os.bin. Refer to this program as an example on how to +access the SD card and how to use the data stack. + +# ttt/ + +Tic-Tac-Toe + +# serial_line_echo/ + +Test serial input/output. + +# 10print/ + +https://10print.org/ + +# interrupts/ + +Testing interrupt handling. + +# stack_test/ + +Testing the data stack. + +# via_test/ + +Testing VIA GPIO. + +# ansi_test/ + +Testing ANSI term commands. diff --git a/sw/ansi_test/Makefile b/sw/asm/ansi_test/Makefile diff --git a/sw/ansi_test/ansi_test.asm b/sw/asm/ansi_test/ansi_test.asm diff --git a/sw/interrupts/Makefile b/sw/asm/interrupts/Makefile diff --git a/sw/interrupts/interrupts.asm b/sw/asm/interrupts/interrupts.asm diff --git a/sw/load_from_card/Makefile b/sw/asm/load_from_card/Makefile diff --git a/sw/asm/load_from_card/load_from_card.asm b/sw/asm/load_from_card/load_from_card.asm @@ -0,0 +1,238 @@ +;;; Copyright 2021 Gerd Beuster (gerd@frombelow.net). This is free +;;; software under the GNU GPL v3 license or any later version. See +;;; COPYING in the root directory for details. + +;;; Start program form SD card. +;;; If we are included in boot.asm, we +;;; should not set the start address +.weak + BOOT_EMBEDDED = false +.endweak +.if !BOOT_EMBEDDED + .include "os.inc" +.endif + + +.dsection code + +.section code + +;;; ----------------------------------- +;;; +;;; Main +;;; +;;; ----------------------------------- + +.section zero_page +delay0: .byte ? +delay1: .byte ? +delay2: .byte ? +.send zero_page + +init: + .block + cld +.if SYMON + jsr io.init_acia + jsr via.init +.endif +.if !BOOT_EMBEDDED + ;; Wait for key to start + jsr io.getc +.endif + #ds.INIT_STACK $7ffd-$0400 + jsr ls_or_autostart + jsr choose_program + bcc execute + jmp ($fffc) ; Reset + .bend + + load_address = $0200 + +choose_program: + .block + lda via.ddra ; Set + ora #%00010000 ; PA4 + sta via.ddra ; as output. + lda #$00 ; Prepare + sta delay0 ; for + sta delay1 ; PA4 + lda #$03 ; blink. + sta delay2 + #io.PRINTSNL "Choose program (0-9). Any other key reloads card." +wait_for_input: + dec delay0 + bne dont_blink + dec delay1 + bne dont_blink + dec delay2 + bne dont_blink + lda #$03 + sta delay2 + lda via.ra ; Blink + eor #%00010000 ; PA4 + sta via.ra +dont_blink: + jsr io.getc_nonblocking + bcs wait_for_input + sec + sbc #'0' + cmp #$0a ; Sets carry if not a digit + pha + pla + rts + .bend + + +execute: + .block + pha + jsr ds.create_stack_frame + #ds.PUSH 4 ; Local variables + pla + pha ; Save application number + ;; Each app has 2**16 blocks, app number is in A, therefore + ;; the address of the app is [#$00, A, #$00, #$01] + #ds.sta_LOCAL 1 + #io.PRINTS "Loading and executing program " + #ds.lda_LOCAL 1 + clc + adc #'0' + jsr io.putc + #io.PRINTSNL "." + lda #$00 + #ds.sta_LOCAL 0 + #ds.sta_LOCAL 2 + lda #$01 + #ds.sta_LOCAL 3 + #mem.COPY_WORD_ABSOLUTE_INDIRECT ds.ptr, sd.data ; Read block + #ds.PUSH $0200 ; to stack + #ds.CALL sd.read_block, [0, 1, 2, 3], #ds.lda_LOCAL + #ds.PULL $0200 ; Reset stack pointer to block read + #mem.COPY_WORD_IMMEDIATE load_address, sd.data ; Set Target address + lda (ds.ptr) ; Number of blocks +read_next_block: + pha + ;; Advance to next block on card + clc + #ds.lda_LOCAL 3 + adc #$01 + #ds.sta_LOCAL 3 + #ds.lda_LOCAL 2 + adc #$00 + #ds.sta_LOCAL 2 + #ds.CALL sd.read_block, [0, 1, 2, 3], #ds.lda_LOCAL ; Read block + #mem.ADD_WORD sd.data, $0200 ; Advance to next block + pla + dec a + bne read_next_block + jsr ds.delete_stack_frame + ;; Done. + ;; Turn off blinkenlight and execute program + lda via.ra + and #%11101111 + sta via.ra + jsr via.init + pla ; Application number stored on stack + jmp load_address ; passed to program executed. + .bend + + + ;; List programs on card +ls_or_autostart: + .block + jsr ds.create_stack_frame + jsr sd.open + ;; List files on card + #mem.COPY_WORD_ABSOLUTE_INDIRECT ds.ptr, sd.data ; Read block + #ds.PUSH $0200 ; to stack + #ds.CALL sd.read_block, [#$00, #$00, #$00, #$00], lda + #ds.PULL $0200 ; Reset stack pointer to point to block read + ldy #$00 + lda (ds.ptr) + cmp #$15 ; Check FS type + beq cont + jmp not_supported +cont: + iny + lda (ds.ptr),y + cmp #$e2 ; Check FS type + beq cont1 + jmp not_supported +cont1: + iny + lda (ds.ptr),y + cmp #$00 ; Check FS version + beq cont2 + jmp not_supported +cont2: + iny + lda (ds.ptr),y + cmp #$ff ; Check autostart + beq list_files ; No autostart + ;; Autostart + pha + jsr ds.delete_stack_frame + pla + jmp execute +list_files: + lda #$00 ; File number +dir_entry_loop: + pha + clc + adc #'0' + jsr io.putc + #io.PRINTS ": " + ;; Each app has 2**16 blocks, therefore + ;; the address of app i is [#$00, #i, #$00, #$01] + lda #$00 + #ds.sta_LOCAL 0 + #ds.sta_LOCAL 2 + lda #$01 + #ds.sta_LOCAL 3 + pla + pha + #ds.sta_LOCAL 1 + #ds.PUSH 4 + #mem.COPY_WORD_ABSOLUTE_INDIRECT ds.ptr, sd.data ; Read block + #ds.PUSH $0200 ; to stack + #ds.CALL sd.read_block, [0, 1, 2, 3], #ds.lda_LOCAL + #ds.PULL $0200 ; Reset stack pointer to block read + lda (ds.ptr) ; Number of blocks + beq filename_printed ; This spot is not occupied + pha + #mem.COPY_WORD_ABSOLUTE_INDIRECT ds.ptr, io.puts_str + #mem.ADD_WORD io.puts_str, $01 ; Start of filename + jsr io.puts + #io.PRINTS " (" + pla + pha + jsr io.putd + pla + cmp #$01 + beq one_block + #io.PRINTS " blocks)" + jmp filename_printed +one_block: + #io.PRINTS " block)" +filename_printed: + jsr io.putnl + #ds.PULL 4 + pla + inc a + cmp #$0a + bcs done + jmp dir_entry_loop +done: + clc + jsr ds.delete_stack_frame + rts +not_supported: + #io.PRINTSNL "This filesystem is not supported." + sec + jsr ds.delete_stack_frame + rts + .bend + +end_of_code: +.send code diff --git a/sw/serial_line_echo/Makefile b/sw/asm/serial_line_echo/Makefile diff --git a/sw/serial_line_echo/serial_line_echo.asm b/sw/asm/serial_line_echo/serial_line_echo.asm diff --git a/sw/stack_test/Makefile b/sw/asm/stack_test/Makefile diff --git a/sw/stack_test/stack_test.asm b/sw/asm/stack_test/stack_test.asm diff --git a/sw/asm/ttt/Makefile b/sw/asm/ttt/Makefile @@ -0,0 +1,14 @@ +TARGET=ttt + +include ../Makefile.common + +test: $(TARGET)_test.bin $(TARGET)_symon_test.bin + +%_test.bin: %.asm + 64tass $(CFLAGS) -DRUN_TESTS=true -DSYMON=false -l $(TARGET)_test.l -L $(TARGET)_test.lst "$<" -o "$@" + +%_symon_test.bin: %.asm + 64tass $(CFLAGS) -DRUN_TESTS=true -DSYMON=true -l $(TARGET)_symon_test.l -L $(TARGET)_symon_test.lst "$<" -o "$@" + +upload_test: $(TARGET).bin + ../../../tools/boot.py $(TARGET)_test.bin diff --git a/sw/ttt/README.txt b/sw/asm/ttt/README.txt diff --git a/sw/ttt/board.asm b/sw/asm/ttt/board.asm diff --git a/sw/ttt/board_test.asm b/sw/asm/ttt/board_test.asm diff --git a/sw/ttt/computer_player.asm b/sw/asm/ttt/computer_player.asm diff --git a/sw/asm/ttt/computer_player_test.asm b/sw/asm/ttt/computer_player_test.asm @@ -0,0 +1,617 @@ +;;; Copyright 2021 Gerd Beuster (gerd@frombelow.net). This is free +;;; software under the GNU GPL v3 license or any later version. See +;;; COPYING in the root directory for details. + +computer_player_test: + .block + jsr clear_memory + ;; jsr mirror_board_test + ;; jsr win_by_column + ;; jsr win_by_diagonal + ;; jsr block_by_column + ;; jsr fork_row_column + ;; jsr test_block_fork + ;; jsr test_play_opposite_corner + ;; jsr test_play_empty_corner + ;; jsr test_perfect_vs_random_computer_player + ;; jsr test_random_vs_perfect_computer_player + jsr test_perfect_vs_perfect_computer_player +loop: + jmp loop + .bend + +clear_memory: + ;; Clear RAM for debugging purposes + .block + lda #<end_of_code + sta tmp + lda #>end_of_code + sta tmp+1 +clear_memory_loop: + lda #$aa + sta (tmp) + inc tmp + bne clear_memory_loop + inc tmp+1 + lda tmp+1 + cmp #$80 + bne clear_memory_loop + rts + .bend + + + +SET_BOARD: .macro + lda #\1 + sta main_board + lda #\2 + sta main_board+1 + lda #\3 + sta main_board+2 + .endm + +CHECK_BOARD: .macro + lda board_mirrored + cmp #\1 + bne check_board_err + lda board_mirrored+1 + cmp #\2 + bne check_board_err + lda board_mirrored+2 + cmp #\3 + beq check_board_cont +check_board_err: + jmp error +check_board_cont: + .endm + +mirror_board_test: + .block + jsr init_board + #mem.STORE_WORD board_mirrored,other_board_ptr + ;; Test rotation of board + ;; One piece on field 0. + ;; Rotation should change nothing + #SET_BOARD %00000011,%00000000,%00000000 + jsr mirror_board + #CHECK_BOARD %00000011,%00000000,%00000000 + ;; First row to first column + #SET_BOARD %00111001,%00000000,%00000000 + jsr mirror_board + #CHECK_BOARD %00000001,%00000010,%00000011 + ;; Second row to Second column + #SET_BOARD %00000000,%00111001,%00000000 + jsr mirror_board + #CHECK_BOARD %00000100,%00001000,%00001100 + ;; Third row to third column + #SET_BOARD %00000000,%00000000,%00111001 + jsr mirror_board + #CHECK_BOARD %00010000,%00100000,%00110000 + ;; Complex board pattern + #SET_BOARD %00100100,%00110011,%00011001 + jsr mirror_board + #CHECK_BOARD %011100,%100001,%011110 +no_error: + #io.PRINTSNL "OK" + rts +error: + #io.PRINTSNL "ERROR" + rts + .bend + + ;; Computer finishes game + ;; by completing column +win_by_column: + ;; First column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 0 + #SET_FIELD piece_x, 3 + jsr play_win_by_column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 0 + #SET_FIELD piece_x, 6 + jsr play_win_by_column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 3 + #SET_FIELD piece_x, 6 + jsr play_win_by_column + ;; Second column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 1 + #SET_FIELD piece_x, 4 + jsr play_win_by_column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 1 + #SET_FIELD piece_x, 7 + jsr play_win_by_column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 4 + #SET_FIELD piece_x, 7 + jsr play_win_by_column + ;; Third column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 2 + #SET_FIELD piece_x, 5 + jsr play_win_by_column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 2 + #SET_FIELD piece_x, 8 + jsr play_win_by_column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 5 + #SET_FIELD piece_x, 8 + jsr play_win_by_column + rts +play_win_by_column: + jsr print_board + jsr io.putnl + lda #piece_x + jsr computer_perfect_ply + pha + jsr print_board + pla + jsr get_game_state + cmp #piece_x + bne win_by_column_error + #io.PRINTSNL "OK" + rts +win_by_column_error: + #io.PRINTSNL "ERROR" + rts + +win_by_diagonal: + ;; + ;; First diagonal + ;; + ;; Lower right field empty + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 0 + #SET_FIELD piece_x, 4 + jsr play_win_by_diagonal + ;; Middle field empty + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 0 + #SET_FIELD piece_x, 8 + jsr play_win_by_diagonal + ;; Upper left field empty + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 4 + #SET_FIELD piece_x, 8 + jsr play_win_by_diagonal + ;; + ;; Second diagonal + ;; + ;; Lower left field empty + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 2 + #SET_FIELD piece_x, 4 + jsr play_win_by_diagonal + ;; Middle field empty + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 2 + #SET_FIELD piece_x, 6 + jsr play_win_by_diagonal + ;; Upper right field empty + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 4 + #SET_FIELD piece_x, 6 + jsr play_win_by_diagonal + rts +play_win_by_diagonal: + jsr print_board + jsr io.putnl + lda #piece_x + jsr computer_perfect_ply + pha + jsr print_board + pla + jsr get_game_state + cmp #piece_x + bne win_by_diagonal_error + #io.PRINTSNL "OK" + rts +win_by_diagonal_error: + #io.PRINTSNL "ERROR" + rts + +block_by_column: + ;; First column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 0 + #SET_FIELD piece_x, 3 + jsr play_block_by_column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 0 + #SET_FIELD piece_x, 6 + jsr play_block_by_column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 3 + #SET_FIELD piece_x, 6 + jsr play_block_by_column + ;; Second column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 1 + #SET_FIELD piece_x, 4 + jsr play_block_by_column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 1 + #SET_FIELD piece_x, 7 + jsr play_block_by_column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 4 + #SET_FIELD piece_x, 7 + jsr play_block_by_column + ;; Third column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 2 + #SET_FIELD piece_x, 5 + jsr play_block_by_column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 2 + #SET_FIELD piece_x, 8 + jsr play_block_by_column + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 5 + #SET_FIELD piece_x, 8 + jsr play_block_by_column + rts +play_block_by_column: + jsr print_board + jsr io.putnl + lda #piece_o + jsr computer_perfect_ply + pha + jsr print_board + pla + jsr io.getc + rts + +fork_row_column: + .block + ;; Row/column fork + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 0 + #SET_FIELD piece_x, 1 + #SET_FIELD piece_o, 2 + #SET_FIELD piece_o, 3 + #SET_FIELD piece_x, 6 + pha + jsr print_board + pla + jsr io.putnl + lda #piece_o + jsr computer_perfect_ply + pha + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,5 + #CHECK_ERROR piece_o + jsr io.getc + ;; Row/diagonal fork + jsr init_board + ;; lda #$ac ; Test runs perfectly with this initialization of the lfsr + ;; sta lfsr.state + ;; sta lfsr.state+1 + jsr computer_perfect_init + #SET_FIELD piece_x, 0 + #SET_FIELD piece_o, 3 + #SET_FIELD piece_x, 6 + jsr io.putnl + pha + jsr print_board + pla + lda #piece_x + jsr computer_perfect_ply + pha + jsr io.putnl + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,2 + #CHECK_ERROR piece_x + jsr io.getc + ;; Another Row/diagonal fork + jsr init_board + jsr computer_perfect_init + #SET_FIELD piece_x, 0 + #SET_FIELD piece_x, 3 + #SET_FIELD piece_o, 6 + #SET_FIELD piece_o, 7 + #SET_FIELD piece_x, 8 + pha + jsr print_board + pla + jsr io.putnl + lda #piece_o + jsr computer_perfect_ply + pha + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,4 + #CHECK_ERROR piece_o + jsr io.getc + rts + .bend + +test_block_fork: + .block + jsr computer_perfect_init + #mem.STORE_WORD main_board,board_ptr + #SET_FIELD piece_x, 0 + #SET_FIELD piece_o, 3 + #SET_FIELD piece_x, 4 + #SET_FIELD piece_o, 8 + jsr io.putnl + pha + jsr print_board + pla + lda #piece_o + jsr computer_perfect_ply + pha + jsr io.putnl + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,1 + #CHECK_ERROR piece_o + jsr io.getc + ;; Check if computer creates + ;; two-in-a-row/column/diagonal + ;; when when a fork by the opponet is + ;; possible. + ;; The following scenario + ;; can be created by the plys + ;; X -> 5 + ;; O -> 1 + ;; X -> 9 + jsr computer_perfect_init + #mem.STORE_WORD main_board,board_ptr + #SET_FIELD piece_o, 0 + #SET_FIELD piece_x, 4 + #SET_FIELD piece_x, 8 + jsr io.putnl + pha + jsr print_board + pla + lda #piece_o + jsr computer_perfect_ply + pha + jsr io.putnl + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,1 + #CHECK_ERROR piece_o + jsr io.getc + ;; The following scenario + ;; can be created by the plys + ;; X -> 1 + ;; O -> 5 + ;; X -> 9 + jsr computer_perfect_init + #mem.STORE_WORD main_board,board_ptr + #SET_FIELD piece_x, 0 + #SET_FIELD piece_o, 4 + #SET_FIELD piece_x, 8 + jsr io.putnl + pha + jsr print_board + pla + lda #piece_o + jsr computer_perfect_ply + pha + jsr io.putnl + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,3 + #CHECK_ERROR piece_o + jsr io.getc + rts + ;; The following scenario + ;; can be created by the plys + ;; X -> 5 + ;; O -> 1 + ;; X -> 9 + jsr computer_perfect_init + #mem.STORE_WORD main_board,board_ptr + #SET_FIELD piece_x, 4 + #SET_FIELD piece_o, 0 + #SET_FIELD piece_x, 8 + jsr io.putnl + pha + jsr print_board + pla + lda #piece_o + jsr computer_perfect_ply + pha + jsr io.putnl + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,2 + #CHECK_ERROR piece_o + jsr io.getc + rts + .bend + +test_play_opposite_corner: + .block + jsr computer_perfect_init + #mem.STORE_WORD main_board,board_ptr + #SET_FIELD piece_x, 0 + #SET_FIELD piece_o, 4 + jsr io.putnl + pha + jsr print_board + pla + lda #piece_o + jsr computer_perfect_ply + pha + jsr io.putnl + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,8 + #CHECK_ERROR piece_o + jsr io.getc + rts + .bend + +test_play_empty_corner: + .block + jsr computer_perfect_init + #mem.STORE_WORD main_board,board_ptr + #SET_FIELD piece_x, 4 + #SET_FIELD piece_o, 0 + jsr io.putnl + pha + jsr print_board + pla + lda #piece_o + jsr computer_perfect_ply + pha + jsr io.putnl + jsr print_board + pla + ;; Check for correct ply + #GET_FIELD main_board,2 + #CHECK_ERROR piece_o + jsr io.getc + rts + .bend + +test_perfect_vs_random_computer_player: + .block +game_loop: + ;; Set player X + #mem.STORE_WORD computer_perfect_init, player_x_init_ptr + #mem.STORE_WORD computer_perfect_ply, player_x_ply_ptr + ;; Set player O + #mem.STORE_WORD computer_random_init, player_o_init_ptr + #mem.STORE_WORD computer_random_ply, player_o_ply_ptr + jsr play_game + jsr get_game_state + cmp #piece_o ; Random player + bne game_loop ; Should never win + #io.PRINTSNL 'Error' +error: + jmp error + .bend +test_random_vs_perfect_computer_player: + .block +game_loop: + ;; Set player X + #mem.STORE_WORD computer_random_init, player_x_init_ptr + #mem.STORE_WORD computer_random_ply, player_x_ply_ptr + ;; Set player O + #mem.STORE_WORD computer_perfect_init, player_o_init_ptr + #mem.STORE_WORD computer_perfect_ply, player_o_ply_ptr + jsr play_game + jsr get_game_state + cmp #piece_x ; Random player + bne game_loop ; Should never win + #io.PRINTSNL 'Error' +error: + jmp error + .bend +test_perfect_vs_perfect_computer_player: + .block +game_loop: + ;; Check if it is time to quit + jsr io.getc_nonblocking + tax + cmp #'q' ; Quit? + bne start + jmp ($fffc) ; Reset +start: + ;; Set player X + #mem.STORE_WORD computer_perfect_init, player_x_init_ptr + #mem.STORE_WORD computer_perfect_ply, player_x_ply_ptr + ;; Set player O + #mem.STORE_WORD computer_perfect_init, player_o_init_ptr + #mem.STORE_WORD computer_perfect_ply, player_o_ply_ptr + jsr play_game + jsr get_game_state + cmp #piece_none ; All games should end in + beq game_loop ; a draw. + #io.PRINTSNL 'Error' +error: + jmp error + .bend + +print_random_ply: + .block + ;; Debug output - Computer ply + pha + phx + clc + adc #'0' + jsr io.putc + #io.PRINTS ' plays ' + pla + pha + adc #'0' + jsr io.putc + jsr io.putnl + plx + pla + rts + .bend + +print_ply: + .block + ;; Debug output - Computer ply + pha + phy + phx + clc + adc #'0' + jsr io.putc + #io.PRINTS ' plays ' + pla + pha + adc #'0' + jsr io.putc + lda #'/' + jsr io.putc + plx + pla + pha + phx + adc #'0' + jsr io.putc + jsr io.putnl + plx + ply + pla + rts + .bend + diff --git a/sw/ttt/human_player.asm b/sw/asm/ttt/human_player.asm diff --git a/sw/ttt/ttt.asm b/sw/asm/ttt/ttt.asm diff --git a/sw/via_test/Makefile b/sw/asm/via_test/Makefile diff --git a/sw/via_test/via_test.asm b/sw/asm/via_test/via_test.asm diff --git a/sw/c/README.md b/sw/c/README.md @@ -0,0 +1 @@ +Some example programs showing how to program Eris 2010 in C. diff --git a/sw/c/blink/Makefile b/sw/c/blink/Makefile @@ -0,0 +1,3 @@ +TARGET=blink + +include ../cc65_eris/Makefile.common diff --git a/sw/c/blink/blink.c b/sw/c/blink/blink.c @@ -0,0 +1,32 @@ +#include <conio.h> +#include "../cc65_eris/os.h" + +void main(void){ + unsigned char ddra; + unsigned int delay; + unsigned char blink; + cputs("Hit a key.\r\n"); + cgetc(); + cputs("Bliniking LED.\r\n"); + // Set PA4 as output + ddra = PEEK(VIA_DDRA); + ddra |= 0x10; + POKE(VIA_DDRA, ddra); + for (;;) { + // Toggle LED on every + // counter overflow. + if (++delay == 0) { + blink = PEEK(VIA_RA); + blink ^= 0x10; + POKE(VIA_RA, blink); + } + if (kbhit()) { + // Key has been pressed. + // Exit if it is 'q' + if (cgetc() == 'q') { + break; + } + } + + } +} diff --git a/sw/c/cc65_eris/Makefile b/sw/c/cc65_eris/Makefile @@ -0,0 +1,18 @@ +all: os.o os_symon.o crt0.o eris2010.lib + +os.o: os.s + ca65 os.s + +os_symon.o: os.s + ca65 -D SYMON os.s -o os_symon.o + +crt0.o: crt0.s + ca65 crt0.s + +eris2010.lib: + cp /usr/share/cc65/lib/none.lib eris2010.lib + ar65 d eris2010.lib cputs.o + ar65 a eris2010.lib crt0.o os.o + +clean: + rm -f *.o *.bin eris2010.lib diff --git a/sw/c/cc65_eris/Makefile.common b/sw/c/cc65_eris/Makefile.common @@ -0,0 +1,24 @@ +ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST)))) +SYSDIR=$(ROOT_DIR)/../cc65_eris + +all: $(TARGET).bin + +$(TARGET).bin: $(TARGET).o $(SYSDIR)/eris2010.lib + ld65 -C $(SYSDIR)/eris2010.cfg -m $(TARGET).map -o $(TARGET).bin \ + $(TARGET).o $(SYSDIR)/eris2010.lib + +$(TARGET)_symon.bin: $(TARGET).o $(SYSDIR)/eris2010_symon.lib + ld65 -C $(SYSDIR)/eris2010_symon.cfg -m $(TARGET).map -o $(TARGET)_symon.bin \ + $(TARGET).o $(SYSDIR)/eris2010_symon.lib + +$(TARGET).o: $(TARGET).c + cc65 -O --cpu 65c02 $(TARGET).c + ca65 $(TARGET).s + + +upload: $(TARGET).bin + ../../../tools/boot.py $(TARGET).bin + +clean: + rm -f *.o *.bin $(TARGET).s $(TARGET).map eris2010.lib + diff --git a/sw/c/cc65_eris/crt0.s b/sw/c/cc65_eris/crt0.s @@ -0,0 +1,53 @@ + ; Based on non/crt0.s from CC65 repository. + .setcpu "65C02" + .include "/usr/share/cc65/asminc/zeropage.inc" + .include "os.inc" +.IFDEF SYMON + .include "../../../../eris2010/roms/os/ds_symon.l" + .include "../../../../eris2010/roms/os/os_symon.l" +.ELSE + .include "../../../../eris2010/roms/os/ds.l" + .include "../../../../eris2010/roms/os/os.l" +.ENDIF + + .export _exit + .export __STARTUP__ : absolute = 1 ; Mark as startup + .export initmainargs + .import zerobss, _main + .import initlib, donelib + .import __STACKSTART__ ; Linker generated + .import __ERIS_STACK_START__ ; Linker generated + .import pusha + + .segment "STARTUP" + + pha ; Application number + ;; Set default IRQ handler + lda #<default_irq_handler + sta ram_end-1 + lda #>default_irq_handler + sta ram_end + jsr init_acia ; Ready to communicate + ;; Initialize CC65 parameter stack + lda #<__STACKSTART__ + ldx #>__STACKSTART__ + sta sp + stx sp+1 + ;; Initialize Eris 2010 OS data stack + lda #<__ERIS_STACK_START__ + sta ptr ; Eris 2010 stack pointer imported from ds.l + lda #>__ERIS_STACK_START__ + sta ptr+1 + jsr zerobss + jsr initlib + pla ; Pass application number to main + jsr pusha + jsr _main +_exit: pha + jsr donelib + pla + jmp ($fffc) ; Reset + rts + +initmainargs: + rts diff --git a/sw/c/cc65_eris/eris2010.cfg b/sw/c/cc65_eris/eris2010.cfg @@ -0,0 +1,39 @@ +# Based on none.cfg from CC65 repository. + +FEATURES { + STARTADDRESS: default = $200; +} +SYMBOLS { + # Two software stacks are defined: One data stack for the Eris 2010 OS + # and the parameter stack of CC65. + # Eris stack occupies $0200 bytes (enough to store one SD card block) + # right below the RAM IRQ vector + __ERIS_STACK_START__: type = weak, value = $7FFD-$0200; + __STACKSIZE__: type = weak, value = $1000; # 4k stack + __STACKSTART__: type = weak, value = __ERIS_STACK_START__; # $7FFE/$7FFF store RAM IRQ vector + __ZPSTART__: type = weak, value = $0011; # From rom_zero_page_end +} +MEMORY { + ZP: file = "", define = yes, start = __ZPSTART__, size = $0100-__ZPSTART__; + MAIN: file = %O, start = %S, size = __STACKSTART__ - __STACKSIZE__ - %S; +} +SEGMENTS { + ZEROPAGE: load = ZP, type = zp; + STARTUP: load = MAIN, type = ro, optional = yes; + LOWCODE: load = MAIN, type = ro, optional = yes; + ONCE: load = MAIN, type = ro, optional = yes; + CODE: load = MAIN, type = rw; + RODATA: load = MAIN, type = rw; + DATA: load = MAIN, type = rw; + BSS: load = MAIN, type = bss, define = yes; +} +FEATURES { + CONDES: type = constructor, + label = __CONSTRUCTOR_TABLE__, + count = __CONSTRUCTOR_COUNT__, + segment = ONCE; + CONDES: type = destructor, + label = __DESTRUCTOR_TABLE__, + count = __DESTRUCTOR_COUNT__, + segment = RODATA; +} diff --git a/sw/c/cc65_eris/os.h b/sw/c/cc65_eris/os.h @@ -0,0 +1,51 @@ +#ifndef OS_H +#define OS_H + +/* For detecting memory leaks. */ +void __fastcall__ get_stack_pointers(unsigned char *hw_sp, unsigned *eris_sp, + unsigned *cc65_sp); + +/* Access to resources */ + +#define IRQ_ISR = 0x7ffe + +#ifdef SYMON + #define ACIA_BASE = 0x8800 +#else + #define ACIA_BASE = 0xc000 +#endif +#define ACIA_DATA_REG ACIA_BASE +#define ACIA_STATUS_REG (ACIA_BASE+0x1) +#define ACIA_CMD_REG (ACIA_BASE+0x2) +#define ACIA_CTRL_REG (ACIA_BASE+0x3) + +#ifdef SYMON + #define VIA_BASE 0x8000 +#else + #define VIA_BASE 0xc800 +#endif +#define VIA_RB VIA_BASE +#define VIA_RA (VIA_BASE+0x1) +#define VIA_DDRB (VIA_BASE+0x2) +#define VIA_DDRA (VIA_BASE+0x3) +#define VIA_T1CL (VIA_BASE+0x4) +#define VIA_T1CH (VIA_BASE+0x5) +#define VIA_T1LL (VIA_BASE+0x6) +#define VIA_T1LH (VIA_BASE+0x7) +#define VIA_T2CL (VIA_BASE+0x8) +#define VIA_T2CH (VIA_BASE+0x9) +#define VIA_SR (VIA_BASE+0xA) +#define VIA_ACR (VIA_BASE+0xB) +#define VIA_PCR (VIA_BASE+0xC) +#define VIA_IFR (VIA_BASE+0xD) +#define VIA_IER (VIA_BASE+0xE) +#define VIA_RA2 (VIA_BASE+0xF) + +// https://github.com/cc65/wiki/wiki/PEEK-and-POKE +#define POKE(addr,val) (*(unsigned char*) (addr) = (val)) +#define POKEW(addr,val) (*(unsigned*) (addr) = (val)) +#define PEEK(addr) (*(unsigned char*) (addr)) +#define PEEKW(addr) (*(unsigned*) (addr)) + +#endif + diff --git a/sw/c/cc65_eris/os.inc b/sw/c/cc65_eris/os.inc @@ -0,0 +1,9 @@ + .setcpu "65C02" + +.IFDEF SYMON + .include "../../../../eris2010/roms/os/io_symon.l" + .include "../../../../eris2010/roms/os/term_symon.l" +.ELSE + .include "../../../../eris2010/roms/os/io.l" + .include "../../../../eris2010/roms/os/term.l" +.ENDIF diff --git a/sw/c/cc65_eris/os.s b/sw/c/cc65_eris/os.s @@ -0,0 +1,359 @@ + .setcpu "65C02" + + .include "os.inc" + .include "../../../../eris2010/roms/os/ds.l" + .include "../../../../eris2010/roms/os/sd.l" + + .include "errno.inc" + + .import popa + .import popax + .import incsp2 + .import pushax + .import __oserror, _OpenDisk + .importzp sp, ptr1 + + ;; CONIO + .export _cgetc + .export _cputc + .export _cputs + .export _cclear + .export _cclearxy + .export _chline + .export _chlinexy + .export _clrscr + .export _cputcxy + .export _cputsxy + .export _cvline + .export _cvlinexy + .export _gotox + .export _gotoy + .export _gotoxy + .export gotoxy + .export _kbhit + .export _revers + .export screensize + .export _textcolor + .export _wherex + .export _wherey + ;; Debugging + .export _get_stack_pointers + ;; DIO + .export _dio_open + .export _dio_close + .export _dio_read + .export _dio_write + +.proc _cgetc: near + jmp getc +.endproc + +.proc _cputc: near + jmp putc +.endproc + +.proc _cputs: near + ;; Send string terminated by '\0' + sta puts_str + stx puts_str+1 + jmp puts +.endproc + + ;; Functions not available: + ;; bgcolor + ;; bordercolor + ;; cpeekc + ;; cpeekcolor + ;; cpeekrevers + ;; cpeeks + ;; cursor + +.proc _cclear: near +print_loop: + cmp #$00 + beq done + dec a + pha + lda #' ' + jsr putc + pla + jmp print_loop +done: + rts +.endproc + +.proc csetcursor: near + ;; Auxillary function, very similar to + ;; "official" function gotoxy. + jsr popa ; y position -> A + inc a ; term.asm starts counting at 1 + pha + jsr popa ; x position -> A + inc a ; term.asm starts counting at 1 + tax + pla + jmp set_cursor ; set_cursor from term.asm +.endproc + +.proc _cclearxy: near + pha ; Length of area to be cleared + jsr csetcursor + pla + jmp _cclear +.endproc + +.proc _chline: near +print_loop: + cmp #$00 + beq done + dec a + pha + lda #'_' + jsr putc + pla + jmp print_loop +done: + rts +.endproc + +.proc _chlinexy: near + pha ; Length of area to be cleared + jsr csetcursor + pla + jmp _chline +.endproc + +.proc _clrscr: near + jsr clear_screen + lda #$01 + tax + jmp set_cursor +.endproc + +.proc _cputcxy: near + pha + jsr csetcursor + pla + jmp _cputc +.endproc + +.proc _cputsxy: near + pha + phx + jsr csetcursor + plx + pla + jmp _cputs +.endproc + +.proc _cvline: near +print_loop: + cmp #$00 + beq done + dec a + pha + lda #'|' + jsr putc + lda #$01 + jsr cursor_down + lda #$01 + jsr cursor_back + pla + jmp print_loop +done: + rts +.endproc + +.proc _cvlinexy: near + pha ; Length of line + jsr csetcursor + pla + jmp _cvline +.endproc + +.proc _gotox: near + inc a ; term.asm starts counting at 1 + pha ; New x position + jsr get_cursor + plx + jmp set_cursor +.endproc + +.proc _gotoy: near + inc a ; Eris 2010 starts at 1 + pha ; New y position + jsr get_cursor + pla + jmp set_cursor +.endproc + +.proc _gotoxy: near + inc a ; term.asm starts counting at 1 + pha ; New y position + jsr popa + inc a ; term.asm starts counting at 1 + tax ; New x position + pla + jsr set_cursor +.endproc + + gotoxy = _gotoxy + +.proc _kbhit: near + ldx #$00 + lda acia_status_reg + and #%00001000 + rts +.endproc + +.proc _revers + ;; In violation of the specification, the old value + ;; is _not_ returned, because we cannot read it. + tax + lda #'m' + cpx #$00 + beq reset_revers + ldx #$07 + jmp send_CSI +reset_revers: + ldx #$1b + jmp send_CSI + .endproc + +.proc screensize + jsr get_screen_size + tay + txa + rts +.endproc + +.proc _textcolor + ;; In violation of the specification, the old value + ;; is _not_ returned, because we cannot read it. + jmp set_color +.endproc + +.proc _wherex + jsr get_cursor + txa + dec a ; term.asm starts counting at 1. + rts +.endproc + +.proc _wherey + jsr get_cursor + dec a ; term.asm starts counting at 1. + rts +.endproc + + ;; This function is used to detect memory leaks + .importzp ptr1 +.proc _get_stack_pointers + sta ptr1 ; First parameter + stx ptr1+1 + lda sp ; = sp (CC65 parameter stack pointer) + sta (ptr1) + lda sp+1 + ldy #$01 + sta (ptr1),y + jsr popa ; Second parameter + sta ptr1 + jsr popa + sta ptr1+1 + lda ptr ; = ptr (Eris 2010 data stack pointer) + sta (ptr1) + lda ptr+1 + ldy #$01 + sta (ptr1),y + jsr popa ; Third parameter + sta ptr1 + jsr popa + sta ptr1+1 + tsx ; = S (Hardware stack pointer) + txa + sta (ptr1) + rts +.endproc + + ;; Open SD card. The value returned (d_handle) is identical to + ;; input parameter device. Set device (or d_handle) to the + ;; application whose files you want to access. + ;; (Technically, read/write uses d_handle as the upper 16 + ;; bytes of the 32 byte SD sector address. These upper 16 bytes + ;; are identical to the application number.) +.proc _dio_open + pha + jsr open + bcs error + lda #EOK + sta __oserror + pla + ldx #$00 + rts +error: + pla + lda #EIO + sta __oserror + lda #$00 + tax + rts +.endproc + +.proc _dio_close + jsr close + lda #EOK + sta __oserror + rts +.endproc + +.proc _dio_read + jsr prepare_read_write_parameters + jsr read_block + jmp check_read_write_error +.endproc + +.proc _dio_write + jsr prepare_read_write_parameters + jsr write_block + jmp check_read_write_error +.endproc + +.proc check_read_write_error + bcs error + jsr delete_stack_frame + lda #EOK + sta __oserror + rts +error: + jsr delete_stack_frame + lda #EIO + sta __oserror + rts +.endproc + +.proc prepare_read_write_parameters + ;; The SD card sector number is a 32 byte value. + ;; We get the two most significant bytes from + ;; d_handle. The two least significant bytes + ;; are passed as parameter sect_num. These + ;; parameters are passed via the Eris 2010 OS + ;; stack. + sta data ; Location of + stx data+1 ; data buffer + jsr create_stack_frame ; Pass block number + ;; sec_num + jsr popax + ldy #$05 + sta (ptr),y + txa + ldy #$04 + sta (ptr),y + ;; d_handle + jsr popax + ldy #$03 + sta (ptr),y + txa + ldy #$02 + sta (ptr),y + rts +.endproc diff --git a/sw/c/conio_test/Makefile b/sw/c/conio_test/Makefile @@ -0,0 +1,3 @@ +TARGET=conio_test + +include ../cc65_eris/Makefile.common diff --git a/sw/c/conio_test/conio_test.c b/sw/c/conio_test/conio_test.c @@ -0,0 +1,112 @@ +#include <stdio.h> +#include <conio.h> +#include "../cc65_eris/os.h" + +void print_stack_pointers(){ + unsigned cc65_sp, eris_sp; + unsigned char hw_sp; + get_stack_pointers(&hw_sp, &eris_sp, &cc65_sp); + cprintf("CC65 SP: %04X Eris SP: %04X HW SP: %02X\r\n", cc65_sp, eris_sp, hw_sp); +} + +void main(void){ + unsigned i, j; + unsigned char c, x, y; + va_list a_list; + + cgetc(); + clrscr(); + print_stack_pointers(); + cputs("Testing implementation of conio libray.\r\n"); + cputs("If you can read this, cputs works.\r\n"); + cprintf("If you can read this, cprintf %s.\r\n", "works"); + cputs("Press a key:"); + c = cgetc(); + clrscr(); + cputs("If this is the first line of an empty screen, clrscr works.\r\n"); + cprintf("If you pressed key \"%c\", cgetc works.\r\n", c); + cputs("Press a key."); + cgetc(); + clrscr(); + gotoxy(2, 1); + cputs("If this line starts at position (2,1), gotoxy works.\r\n"); + cputs("Press a key."); + cgetc(); + gotoxy(48, 1); + cclear(5); + gotoxy(0, 2); + cputs("If \"works\" above has been deleted, cclear works.\r\n"); + cputs("Press a key."); + cgetc(); + cclearxy(42, 2, 5); + gotoxy(0, 4); + cputs("If \"works\" above has been deleted, cclearxy works.\r\n"); + cputs("If this is a horizontal line of of length 3, chline works: "); + chline(3); + gotoxy(0, 6); + cputs("If this is a horizontal line of of length 3, chlinexy works: "); + gotoxy(0, 0); + chlinexy(61, 6, 3); + cputs("\r\ncputc : "); + cputc('O'); + cputc('K'); + cputsxy(0, 7, "cputcxy/cputsxy: "); + cputcxy(17, 7, 'O'); + cputcxy(18, 7, 'K'); + gotoxy(5,8); + cputs("This is a vertical line at x=5:\r\n"); + gotox(5); + cvline(5); + cputs("And here is a vertical line of length 3:\r\n"); + gotoy(14); + cvlinexy(5,15,3); + cputs("\r\nBusy waiting for pressing a key"); + i = 0; + while (!kbhit()) { + if((++i % 1000) == 0) { + cputc('.'); + } + } + c = cgetc(); + cprintf("\r\nYou pushed: \"%c\"\r\n", c); + cputs("Some text in "); + revers(1); + cputs("revers"); + revers(0); + cputs(".\r\n"); + screensize(&x, &y); + cprintf("The screensize is (%d, %d)\r\n", x, y); + cputs("Press a key."); + cgetc(); + for(i = 0; i < 0x100; i++) { + textcolor(i); + cprintf("Textcolor %d\r\n", i); + } + cputs("Press a key."); + cgetc(); + clrscr(); + gotoxy(2, 3); + x = wherex(); + y = wherey(); + cprintf("The current cursor position is (%d, %d)\r\n", x, y); + va_start(a_list, i); + vcprintf("vcprintf\r\n", a_list); + va_end(); + print_stack_pointers(); + cputs("*********************\r\n"); + cputs("conio test completed.\r\n"); + cputs("*********************\r\n"); + + cputs("\r\n\r\nNow for something completely different.\r\n"); + cprintf("Here are the results of some calculations:\r\n"); + j = 3; + cprintf("%d * 10 = %d\r\n", j, 10*j); + i = 3; + ++i; + cprintf("%d / %d = %d\r\n", j*10, i, j*10/i); + i = 0; + cputs("\r\nSerial echo:\r\n"); + while(((c = cgetc()) != '\r') && (i++ <= 10)){ + cputc(c); + } +} diff --git a/sw/c/dio_test/Makefile b/sw/c/dio_test/Makefile @@ -0,0 +1,3 @@ +TARGET=dio_test + +include ../cc65_eris/Makefile.common diff --git a/sw/c/dio_test/dio_test.c b/sw/c/dio_test/dio_test.c @@ -0,0 +1,127 @@ +#include <stdio.h> +#include <stdlib.h> +#include <conio.h> +#include <errno.h> +#include <dio.h> +#include "../cc65_eris/os.h" + +void print_stack_pointers() { + unsigned cc65_sp, eris_sp; + unsigned char hw_sp; + get_stack_pointers(&hw_sp, &eris_sp, &cc65_sp); + cprintf("CC65 SP: %04X Eris SP: %04X HW SP: %02X\r\n", cc65_sp, eris_sp, hw_sp); +} + +void print_buffer(unsigned char *buffer) { + + unsigned i; + + for (i = 0; i < 512; i++) { + cprintf("%02X ", buffer[i]); + } + cputs("\r\n"); +} + +void read_test(dhandle_t handle) { + + unsigned sect_num; + unsigned *irq; + unsigned char *buffer; + buffer = malloc(512); + + handle = (void *) 0x0000; + for(sect_num = 0; sect_num < 5; sect_num++) { + irq = (unsigned *) 0x7FFE; + cprintf("IRQ vector: %04X\r\n", *irq); + cprintf("Reading handle sector %d %d.\r\n", handle, sect_num); + print_stack_pointers(); + dio_read(handle, sect_num, buffer); + print_stack_pointers(); + if(_oserror != 0) { + cprintf("_oserror is %d\r\n", _oserror); + } + else { + print_buffer(buffer); + } + cputs("\r\n"); + } +} + +void write_test(dhandle_t handle) { + + unsigned sect_num; + unsigned i; + unsigned *irq; + unsigned char *buffer; + buffer = malloc(512); + + handle = (void *) 0x000F; + sect_num = 0x0010; + irq = (unsigned *) 0x7FFE; + cprintf("IRQ vector: %04X\r\n", *irq); + cprintf("Reading handle sector %d %d.\r\n", handle, sect_num); + print_stack_pointers(); + dio_read(handle, sect_num, buffer); + if(_oserror != 0) + goto error; + print_buffer(buffer); + for (i = 0; i < 512; i ++) { + buffer[i] = 0xAA; + } + cputs("Writing to SD card.\r\n"); + dio_write(handle, sect_num, buffer); + if(_oserror != 0) + goto error; + cputs("Reading SD card.\r\n"); + dio_read(handle, sect_num, buffer); + if(_oserror != 0) + goto error; + print_buffer(buffer); + for (i = 0; i < 512; i ++) { + buffer[i] = 0xBB; + } + cputs("Writing to SD card.\r\n"); + dio_write(handle, sect_num, buffer); + if(_oserror != 0) + goto error; + cputs("Reading SD card.\r\n"); + dio_read(handle, sect_num, buffer); + if(_oserror != 0) + goto error; + print_buffer(buffer); + for (i = 0; i < 512; i ++) { + buffer[i] = i; + } + dio_write(handle, sect_num, buffer); + irq = (unsigned *) 0x7FFE; + cprintf("IRQ vector: %04X\r\n", *irq); + print_stack_pointers(); + return; + + error: + cprintf("_oserror is %d\r\n", _oserror); +} + +void main(unsigned char app_number) { + + dhandle_t handle; + + cgetc(); + clrscr(); + cputs("DIO test.\r\n"); + cprintf("My application number is %d.\r\n", app_number); + cgetc(); + print_stack_pointers(); + cputs("Opening card\r\n"); + handle = dio_open(app_number); + if(_oserror != 0) { + cprintf("_oserror is %d\r\n", _oserror); + goto done; + } + read_test(handle); + write_test(handle); + dio_close(handle); + done: + cputs("Done.\r\n"); + cgetc(); +}; diff --git a/sw/c/fibonacci/Makefile b/sw/c/fibonacci/Makefile @@ -0,0 +1,3 @@ +TARGET=fibonacci + +include ../cc65_eris/Makefile.common diff --git a/sw/c/fibonacci/fibonacci.c b/sw/c/fibonacci/fibonacci.c @@ -0,0 +1,25 @@ +#include <conio.h> +#include <limits.h> + +void fibonacci(unsigned long a, unsigned long b) { + unsigned long r = a + b; + static const unsigned long max_param = ULONG_MAX/2; + cprintf("%lu\r\n", r); + if (r < max_param) + fibonacci(b, r); +} + +void main(void){ + for(;;) { + cprintf("0\r\n"); + cprintf("1\r\n"); + fibonacci(0, 1); + if (kbhit()) { + // Key has been pressed. + // Exit if it is 'q' + if (cgetc() == 'q') { + break; + } + } + } +} diff --git a/sw/load_from_card/load_from_card.asm b/sw/load_from_card/load_from_card.asm @@ -1,236 +0,0 @@ -;;; Copyright 2021 Gerd Beuster (gerd@frombelow.net). This is free -;;; software under the GNU GPL v3 license or any later version. See -;;; COPYING in the root directory for details. - -;;; Start program form SD card. -;;; If we are included in boot.asm, we -;;; should not set the start address -.weak - BOOT_EMBEDDED = false -.endweak -.if !BOOT_EMBEDDED - .include "os.inc" -.endif - - -.dsection code - -.section code - -;;; ----------------------------------- -;;; -;;; Main -;;; -;;; ----------------------------------- - -.section zero_page -delay0: .byte ? -delay1: .byte ? -delay2: .byte ? -.send zero_page - -init: - .block - cld -.if SYMON - jsr io.init_acia - jsr via.init -.endif -.if !BOOT_EMBEDDED - ;; Wait for key to start - jsr io.getc -.endif - #ds.INIT_STACK $7fff-$0400 - jsr ls_or_autostart - jsr choose_program - bcc execute - jmp ($fffc) ; Reset - .bend - - load_address = $0200 - -choose_program: - .block - lda via.ddra ; Set - ora #%00010000 ; PA4 - sta via.ddra ; as output. - lda #$00 ; Prepare - sta delay0 ; for - sta delay1 ; PA4 - lda #$03 ; blink. - sta delay2 - #io.PRINTSNL "Choose program (0-9). Any other key reloads card." -wait_for_input: - dec delay0 - bne dont_blink - dec delay1 - bne dont_blink - dec delay2 - bne dont_blink - lda #$03 - sta delay2 - lda via.ra ; Blink - eor #%00010000 ; PA4 - sta via.ra -dont_blink: - jsr io.getc_nonblocking - bcs wait_for_input - sec - sbc #'0' - cmp #$0a ; Sets carry if not a digit - pha - pla - rts - .bend - - -execute: - .block - pha - jsr ds.create_stack_frame - #ds.PUSH 4 ; Local variables - pla - ;; Each app has 2**16 blocks, app number is in A, therefore - ;; the address of the app is [#$00, A, #$00, #$01] - #ds.sta_LOCAL 1 - #io.PRINTS "Loading and executing program " - #ds.lda_LOCAL 1 - clc - adc #'0' - jsr io.putc - #io.PRINTSNL "." - lda #$00 - #ds.sta_LOCAL 0 - #ds.sta_LOCAL 2 - lda #$01 - #ds.sta_LOCAL 3 - #mem.COPY_WORD_ABSOLUTE_INDIRECT ds.ptr, sd.data ; Read block - #ds.PUSH $0200 ; to stack - #ds.CALL sd.read_block, [0, 1, 2, 3], #ds.lda_LOCAL - #ds.PULL $0200 ; Reset stack pointer to block read - #mem.COPY_WORD_IMMEDIATE load_address, sd.data ; Set Target address - lda (ds.ptr) ; Number of blocks -read_next_block: - pha - ;; Advance to next block on card - clc - #ds.lda_LOCAL 3 - adc #$01 - #ds.sta_LOCAL 3 - #ds.lda_LOCAL 2 - adc #$00 - #ds.sta_LOCAL 2 - #ds.CALL sd.read_block, [0, 1, 2, 3], #ds.lda_LOCAL ; Read block - #mem.ADD_WORD sd.data, $0200 ; Advance to next block - pla - dec a - bne read_next_block - jsr ds.delete_stack_frame - ;; Done. - ;; Turn off blinkenlight and execute program - lda via.ra - and #%11101111 - sta via.ra - jsr via.init - jmp load_address - .bend - - - ;; List programs on card -ls_or_autostart: - .block - jsr ds.create_stack_frame - jsr sd.open - ;; List files on card - #mem.COPY_WORD_ABSOLUTE_INDIRECT ds.ptr, sd.data ; Read block - #ds.PUSH $0200 ; to stack - #ds.CALL sd.read_block, [#$00, #$00, #$00, #$00], lda - #ds.PULL $0200 ; Reset stack pointer to point to block read - ldy #$00 - lda (ds.ptr) - cmp #$15 ; Check FS type - beq cont - jmp not_supported -cont: - iny - lda (ds.ptr),y - cmp #$e2 ; Check FS type - beq cont1 - jmp not_supported -cont1: - iny - lda (ds.ptr),y - cmp #$00 ; Check FS version - beq cont2 - jmp not_supported -cont2: - iny - lda (ds.ptr),y - cmp #$ff ; Check autostart - beq list_files ; No autostart - ;; Autostart - pha - jsr ds.delete_stack_frame - pla - jmp execute -list_files: - lda #$00 ; File number -dir_entry_loop: - pha - clc - adc #'0' - jsr io.putc - #io.PRINTS ": " - ;; Each app has 2**16 blocks, therefore - ;; the address of app i is [#$00, #i, #$00, #$01] - lda #$00 - #ds.sta_LOCAL 0 - #ds.sta_LOCAL 2 - lda #$01 - #ds.sta_LOCAL 3 - pla - pha - #ds.sta_LOCAL 1 - #ds.PUSH 4 - #mem.COPY_WORD_ABSOLUTE_INDIRECT ds.ptr, sd.data ; Read block - #ds.PUSH $0200 ; to stack - #ds.CALL sd.read_block, [0, 1, 2, 3], #ds.lda_LOCAL - #ds.PULL $0200 ; Reset stack pointer to block read - lda (ds.ptr) ; Number of blocks - beq filename_printed ; This spot is not occupied - pha - #mem.COPY_WORD_ABSOLUTE_INDIRECT ds.ptr, io.puts_str - #mem.ADD_WORD io.puts_str, $01 ; Start of filename - jsr io.puts - #io.PRINTS " (" - pla - pha - jsr io.putd - pla - cmp #$01 - beq one_block - #io.PRINTS " blocks)" - jmp filename_printed -one_block: - #io.PRINTS " block)" -filename_printed: - jsr io.putnl - #ds.PULL 4 - pla - inc a - cmp #$0a - bcs done - jmp dir_entry_loop -done: - clc - jsr ds.delete_stack_frame - rts -not_supported: - #io.PRINTSNL "This filesystem is not supported." - sec - jsr ds.delete_stack_frame - rts - .bend - -end_of_code: -.send code diff --git a/sw/ttt/Makefile b/sw/ttt/Makefile @@ -1,14 +0,0 @@ -TARGET=ttt - -include ../Makefile.common - -test: $(TARGET)_test.bin $(TARGET)_symon_test.bin - -%_test.bin: %.asm - 64tass $(CFLAGS) -DRUN_TESTS=true -DSYMON=false -l $(TARGET)_test.l -L $(TARGET)_test.lst "$<" -o "$@" - -%_symon_test.bin: %.asm - 64tass $(CFLAGS) -DRUN_TESTS=true -DSYMON=true -l $(TARGET)_symon_test.l -L $(TARGET)_symon_test.lst "$<" -o "$@" - -upload_test: $(TARGET).bin - ../../tools/boot.py $(TARGET)_test.bin diff --git a/sw/ttt/computer_player_test.asm b/sw/ttt/computer_player_test.asm @@ -1,610 +0,0 @@ -;;; Copyright 2021 Gerd Beuster (gerd@frombelow.net). This is free -;;; software under the GNU GPL v3 license or any later version. See -;;; COPYING in the root directory for details. - -computer_player_test: - .block - jsr clear_memory - ;; jsr mirror_board_test - ;; jsr win_by_column - ;; jsr win_by_diagonal - ;; jsr block_by_column - ;; jsr fork_row_column - ;; jsr test_block_fork - ;; jsr test_play_opposite_corner - ;; jsr test_play_empty_corner - ;; jsr test_perfect_vs_random_computer_player - ;; jsr test_random_vs_perfect_computer_player - jsr test_perfect_vs_perfect_computer_player -loop: - jmp loop - .bend - -clear_memory: - ;; Clear RAM for debugging purposes - .block - lda #<end_of_code - sta tmp - lda #>end_of_code - sta tmp+1 -clear_memory_loop: - lda #$aa - sta (tmp) - inc tmp - bne clear_memory_loop - inc tmp+1 - lda tmp+1 - cmp #$80 - bne clear_memory_loop - rts - .bend - - - -SET_BOARD: .macro - lda #\1 - sta main_board - lda #\2 - sta main_board+1 - lda #\3 - sta main_board+2 - .endm - -CHECK_BOARD: .macro - lda board_mirrored - cmp #\1 - bne check_board_err - lda board_mirrored+1 - cmp #\2 - bne check_board_err - lda board_mirrored+2 - cmp #\3 - beq check_board_cont -check_board_err: - jmp error -check_board_cont: - .endm - -mirror_board_test: - .block - jsr init_board - #mem.STORE_WORD board_mirrored,other_board_ptr - ;; Test rotation of board - ;; One piece on field 0. - ;; Rotation should change nothing - #SET_BOARD %00000011,%00000000,%00000000 - jsr mirror_board - #CHECK_BOARD %00000011,%00000000,%00000000 - ;; First row to first column - #SET_BOARD %00111001,%00000000,%00000000 - jsr mirror_board - #CHECK_BOARD %00000001,%00000010,%00000011 - ;; Second row to Second column - #SET_BOARD %00000000,%00111001,%00000000 - jsr mirror_board - #CHECK_BOARD %00000100,%00001000,%00001100 - ;; Third row to third column - #SET_BOARD %00000000,%00000000,%00111001 - jsr mirror_board - #CHECK_BOARD %00010000,%00100000,%00110000 - ;; Complex board pattern - #SET_BOARD %00100100,%00110011,%00011001 - jsr mirror_board - #CHECK_BOARD %011100,%100001,%011110 -no_error: - #io.PRINTSNL "OK" - rts -error: - #io.PRINTSNL "ERROR" - rts - .bend - - ;; Computer finishes game - ;; by completing column -win_by_column: - ;; First column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 3 - jsr play_win_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 6 - jsr play_win_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 3 - #SET_FIELD piece_x, 6 - jsr play_win_by_column - ;; Second column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 1 - #SET_FIELD piece_x, 4 - jsr play_win_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 1 - #SET_FIELD piece_x, 7 - jsr play_win_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 4 - #SET_FIELD piece_x, 7 - jsr play_win_by_column - ;; Third column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 2 - #SET_FIELD piece_x, 5 - jsr play_win_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 2 - #SET_FIELD piece_x, 8 - jsr play_win_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 5 - #SET_FIELD piece_x, 8 - jsr play_win_by_column - rts -play_win_by_column: - jsr print_board - jsr io.putnl - lda #piece_x - jsr computer_perfect_ply - pha - jsr print_board - pla - jsr get_game_state - cmp #piece_x - bne win_by_column_error - #io.PRINTSNL "OK" - rts -win_by_column_error: - #io.PRINTSNL "ERROR" - rts - -win_by_diagonal: - ;; - ;; First diagonal - ;; - ;; Lower right field empty - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 4 - jsr play_win_by_diagonal - ;; Middle field empty - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 8 - jsr play_win_by_diagonal - ;; Upper left field empty - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 4 - #SET_FIELD piece_x, 8 - jsr play_win_by_diagonal - ;; - ;; Second diagonal - ;; - ;; Lower left field empty - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 2 - #SET_FIELD piece_x, 4 - jsr play_win_by_diagonal - ;; Middle field empty - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 2 - #SET_FIELD piece_x, 6 - jsr play_win_by_diagonal - ;; Upper right field empty - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 4 - #SET_FIELD piece_x, 6 - jsr play_win_by_diagonal - rts -play_win_by_diagonal: - jsr print_board - jsr io.putnl - lda #piece_x - jsr computer_perfect_ply - pha - jsr print_board - pla - jsr get_game_state - cmp #piece_x - bne win_by_diagonal_error - #io.PRINTSNL "OK" - rts -win_by_diagonal_error: - #io.PRINTSNL "ERROR" - rts - -block_by_column: - ;; First column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 3 - jsr play_block_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 6 - jsr play_block_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 3 - #SET_FIELD piece_x, 6 - jsr play_block_by_column - ;; Second column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 1 - #SET_FIELD piece_x, 4 - jsr play_block_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 1 - #SET_FIELD piece_x, 7 - jsr play_block_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 4 - #SET_FIELD piece_x, 7 - jsr play_block_by_column - ;; Third column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 2 - #SET_FIELD piece_x, 5 - jsr play_block_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 2 - #SET_FIELD piece_x, 8 - jsr play_block_by_column - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 5 - #SET_FIELD piece_x, 8 - jsr play_block_by_column - rts -play_block_by_column: - jsr print_board - jsr io.putnl - lda #piece_o - jsr computer_perfect_ply - pha - jsr print_board - pla - jsr io.getc - rts - -fork_row_column: - .block - ;; Row/column fork - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 1 - #SET_FIELD piece_o, 2 - #SET_FIELD piece_o, 3 - #SET_FIELD piece_x, 6 - pha - jsr print_board - pla - jsr io.putnl - lda #piece_o - jsr computer_perfect_ply - pha - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,5 - #CHECK_ERROR piece_o - jsr io.getc - ;; Row/diagonal fork - jsr init_board - ;; lda #$ac ; Test runs perfectly with this initialization of the lfsr - ;; sta lfsr.state - ;; sta lfsr.state+1 - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_o, 3 - #SET_FIELD piece_x, 6 - jsr io.putnl - pha - jsr print_board - pla - lda #piece_x - jsr computer_perfect_ply - pha - jsr io.putnl - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,2 - #CHECK_ERROR piece_x - jsr io.getc - ;; Another Row/diagonal fork - jsr init_board - jsr computer_perfect_init - #SET_FIELD piece_x, 0 - #SET_FIELD piece_x, 3 - #SET_FIELD piece_o, 6 - #SET_FIELD piece_o, 7 - #SET_FIELD piece_x, 8 - pha - jsr print_board - pla - jsr io.putnl - lda #piece_o - jsr computer_perfect_ply - pha - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,4 - #CHECK_ERROR piece_o - jsr io.getc - rts - .bend - -test_block_fork: - .block - jsr computer_perfect_init - #mem.STORE_WORD main_board,board_ptr - #SET_FIELD piece_x, 0 - #SET_FIELD piece_o, 3 - #SET_FIELD piece_x, 4 - #SET_FIELD piece_o, 8 - jsr io.putnl - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - jsr io.putnl - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,1 - #CHECK_ERROR piece_o - jsr io.getc - ;; Check if computer creates - ;; two-in-a-row/column/diagonal - ;; when when a fork by the opponet is - ;; possible. - ;; The following scenario - ;; can be created by the plys - ;; X -> 5 - ;; O -> 1 - ;; X -> 9 - jsr computer_perfect_init - #mem.STORE_WORD main_board,board_ptr - #SET_FIELD piece_o, 0 - #SET_FIELD piece_x, 4 - #SET_FIELD piece_x, 8 - jsr io.putnl - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - jsr io.putnl - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,1 - #CHECK_ERROR piece_o - jsr io.getc - ;; The following scenario - ;; can be created by the plys - ;; X -> 1 - ;; O -> 5 - ;; X -> 9 - jsr computer_perfect_init - #mem.STORE_WORD main_board,board_ptr - #SET_FIELD piece_x, 0 - #SET_FIELD piece_o, 4 - #SET_FIELD piece_x, 8 - jsr io.putnl - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - jsr io.putnl - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,3 - #CHECK_ERROR piece_o - jsr io.getc - rts - ;; The following scenario - ;; can be created by the plys - ;; X -> 5 - ;; O -> 1 - ;; X -> 9 - jsr computer_perfect_init - #mem.STORE_WORD main_board,board_ptr - #SET_FIELD piece_x, 4 - #SET_FIELD piece_o, 0 - #SET_FIELD piece_x, 8 - jsr io.putnl - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - jsr io.putnl - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,2 - #CHECK_ERROR piece_o - jsr io.getc - rts - .bend - -test_play_opposite_corner: - .block - jsr computer_perfect_init - #mem.STORE_WORD main_board,board_ptr - #SET_FIELD piece_x, 0 - #SET_FIELD piece_o, 4 - jsr io.putnl - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - jsr io.putnl - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,8 - #CHECK_ERROR piece_o - jsr io.getc - rts - .bend - -test_play_empty_corner: - .block - jsr computer_perfect_init - #mem.STORE_WORD main_board,board_ptr - #SET_FIELD piece_x, 4 - #SET_FIELD piece_o, 0 - jsr io.putnl - pha - jsr print_board - pla - lda #piece_o - jsr computer_perfect_ply - pha - jsr io.putnl - jsr print_board - pla - ;; Check for correct ply - #GET_FIELD main_board,2 - #CHECK_ERROR piece_o - jsr io.getc - rts - .bend - -test_perfect_vs_random_computer_player: - .block -game_loop: - ;; Set player X - #mem.STORE_WORD computer_perfect_init, player_x_init_ptr - #mem.STORE_WORD computer_perfect_ply, player_x_ply_ptr - ;; Set player O - #mem.STORE_WORD computer_random_init, player_o_init_ptr - #mem.STORE_WORD computer_random_ply, player_o_ply_ptr - jsr play_game - jsr get_game_state - cmp #piece_o ; Random player - bne game_loop ; Should never win - #io.PRINTSNL 'Error' -error: - jmp error - .bend -test_random_vs_perfect_computer_player: - .block -game_loop: - ;; Set player X - #mem.STORE_WORD computer_random_init, player_x_init_ptr - #mem.STORE_WORD computer_random_ply, player_x_ply_ptr - ;; Set player O - #mem.STORE_WORD computer_perfect_init, player_o_init_ptr - #mem.STORE_WORD computer_perfect_ply, player_o_ply_ptr - jsr play_game - jsr get_game_state - cmp #piece_x ; Random player - bne game_loop ; Should never win - #io.PRINTSNL 'Error' -error: - jmp error - .bend -test_perfect_vs_perfect_computer_player: - .block -game_loop: - ;; Set player X - #mem.STORE_WORD computer_perfect_init, player_x_init_ptr - #mem.STORE_WORD computer_perfect_ply, player_x_ply_ptr - ;; Set player O - #mem.STORE_WORD computer_perfect_init, player_o_init_ptr - #mem.STORE_WORD computer_perfect_ply, player_o_ply_ptr - jsr play_game - jsr get_game_state - cmp #piece_none ; All games should end in - beq game_loop ; a draw. - #io.PRINTSNL 'Error' -error: - jmp error - .bend - -print_random_ply: - .block - ;; Debug output - Computer ply - pha - phx - clc - adc #'0' - jsr io.putc - #io.PRINTS ' plays ' - pla - pha - adc #'0' - jsr io.putc - jsr io.putnl - plx - pla - rts - .bend - -print_ply: - .block - ;; Debug output - Computer ply - pha - phy - phx - clc - adc #'0' - jsr io.putc - #io.PRINTS ' plays ' - pla - pha - adc #'0' - jsr io.putc - lda #'/' - jsr io.putc - plx - pla - pha - phx - adc #'0' - jsr io.putc - jsr io.putnl - plx - ply - pla - rts - .bend - diff --git a/tools/boot.py b/tools/boot.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Upload software to 8-bit computer""" +"""Upload software to Eris 2010""" import serial import sys diff --git a/tools/kfs.py b/tools/kfs.py @@ -31,10 +31,12 @@ class KFS: def check_device_file(self, device): # Find device # - # Security check: Only small SD cards of 1 GB, 2GB, or 4 GB are accepted. + # Security check: Only small SD cards of 1 GB, 2GB, 4 GB, or 8 GB are accepted. known_devices = [['Generic-', 'SD_MMC', '512', '968.8M'], ['Generic', 'STORAGE_DEVICE', '512', '968.8M'], ['Generic', 'STORAGE_DEVICE', '512', '1.9G'], + ['Generic', 'STORAGE_DEVICE', '512', '7.2G'], + ['Generic', 'STORAGE_DEVICE', '512', '7.3G'], ['Generic', 'STORAGE_DEVICE', '512', '3.8G']] cmd = os.popen('lsblk --list -o name,vendor,model,phy-sec,size ' + device)