I am interested in writing a game for the 6502. I am wondering what to write it in. I have thought about BASIC but don't want the limitations on it. The other alternative I am thinking of is using 6502 assembly language. What do you think?
Writing a Game
Writing a Game
Hay,
I am interested in writing a game for the 6502. I am wondering what to write it in. I have thought about BASIC but don't want the limitations on it. The other alternative I am thinking of is using 6502 assembly language. What do you think?
I am interested in writing a game for the 6502. I am wondering what to write it in. I have thought about BASIC but don't want the limitations on it. The other alternative I am thinking of is using 6502 assembly language. What do you think?
Well, I'm going to plug Forth here. Though interpreted, it's significantly faster than BASIC (to be sure, many of Atari's stand-up arcade games were written in Forth!), and is significantly more compact than BASIC in many cases as well.
However, unless you attain an advanced understanding of the Forth philosophy, it will appear as a write-only language. Understand that Forth isn't write-only -- you just need to learn how its symbology works.
When Forth was first designed by Chuck Moore, it was developed on a 32-bit architecture. And in recognizing that the overwhelming majority of Forth words could be distinguished based on word name length and the first three characters (which conveniently fits in a single 32-bit word!), it follows that you can then fit more source code per line by simply not typing the full names of words. The only time you'd want to is to disambiguate multiple words with the same prefixes (e.g., BEGIN and BEGAN both are the same length and begin with BEG, so both would map to the same word. Bad idea. Instead, you could say BEGIN and BEGAN?, since the latter is more likely an interrogation anyway. But, then, you could save two letters per word and just use BEG and BEG?. Since the entry method of choice in the day was 80-column punched cards, software could be written with fewer cards this way. See where this is going?)
One of the rules of programming in traditional programming languages is to never use single-letter, or otherwise non-descript, variable names or function names UNLESS the definition and intention of the variable can also fit on the same screen for easy reference. In Forth, this happens everywhere.
For example, in my VIBE text editor (VI-like Block Editor, for Forth), I need to draw a line of 64 - characters for a horizontal bar. Instead of writing something like 64 0 DO ." -" LOOP or whatever, I just wrote a few words like this:
I think the intention is clear here. If I want a horizontal bar of 64 dashes, I invoke 64-s. The advantage of writing a fixed-length loop like this is that it is ultimately faster (no conditionals to execute) on some architectures, it doesn't take that much more space (if any at all, depending on the sophistication of the underlying Forth compiler), and uses absolutely zero data stack space. Your mileage may vary depending upon your specific Forth and your specific CPU architecture (for the record, I coded it this way because I code better in a declarative style rather than an imperative style). But I'm digressing.
The point is, word names like 4-s are quite common in Forth. To the uninitiated, they look like bizarre hieroglyphics. Even an APL programmer would be swimming in utter uncertainty. But when viewed in context, its intention is patently clear, even if you didn't know what ." itself did.
Another example is displaying the ASCII chart on the screen:
Pretty clear what ascii-chart does, provided you look in context. In isolation, there is no clue as to what 16r does. Only when reviewing the definition of 16r do we see that it's rendering 16 rows, where each row is itself defined as 16 columns.
Also, because 4-s, r, and c are not likely to be used anywhere else after the definition of 64-s or ascii-chart, I am free to redefine these words as I please, if I want. In a game situation, this may be something where I might want to compute the difference of 4 minus 's', for some idea of what 's' is at the time, I might honestly use 4-s to make it happen. If I want a word to represent the right-hand movement of a ship, I might call it r. Etc.
(Note: In this way, also, Forth fully supports the ideals of modular programming as well, including hiding of internal implementation details. As long as you don't redefine the public symbols of 'a module,' you're OK.)
Obligatory disclaimer: in NO WAY am I slandering other programming languages by revealing these "33rd degree Masonic Forth philosophy keys" as some of Forth's strongest points. I am a HUGE fan of the Oberon programming language, for example, which is the diametric opposite of everything Forth stands for (extremely rigid type checking, fully qualified Module.Object naming conventions, rigidly defined symbol visibility flags between modules, etc). But for this purpose, and indeed for most of my coding needs, Forth fills my requirements best.
Forth's small footprint and excellent speed of development is well documented elsewhere, not only here on this forum but also on the web in general. Much of the argumentation supporting APL and its descendent languages applies equally to Forth as well, for the same reasons (as do much of the argumentation in opposition to it too).
For a unique insight into how experienced Forth coders work with Forth, check out the Thinking Forth book, now online at SourceForge ( http://thinking-forth.sourceforge.net/ ). Strongly recommended reading, even if you choose to never use Forth.
However, unless you attain an advanced understanding of the Forth philosophy, it will appear as a write-only language. Understand that Forth isn't write-only -- you just need to learn how its symbology works.
When Forth was first designed by Chuck Moore, it was developed on a 32-bit architecture. And in recognizing that the overwhelming majority of Forth words could be distinguished based on word name length and the first three characters (which conveniently fits in a single 32-bit word!), it follows that you can then fit more source code per line by simply not typing the full names of words. The only time you'd want to is to disambiguate multiple words with the same prefixes (e.g., BEGIN and BEGAN both are the same length and begin with BEG, so both would map to the same word. Bad idea. Instead, you could say BEGIN and BEGAN?, since the latter is more likely an interrogation anyway. But, then, you could save two letters per word and just use BEG and BEG?. Since the entry method of choice in the day was 80-column punched cards, software could be written with fewer cards this way. See where this is going?)
One of the rules of programming in traditional programming languages is to never use single-letter, or otherwise non-descript, variable names or function names UNLESS the definition and intention of the variable can also fit on the same screen for easy reference. In Forth, this happens everywhere.
For example, in my VIBE text editor (VI-like Block Editor, for Forth), I need to draw a line of 64 - characters for a horizontal bar. Instead of writing something like 64 0 DO ." -" LOOP or whatever, I just wrote a few words like this:
Code: Select all
: 4-s ." ----" ;
: 16-s 4-s 4-s 4-s 4-s ;
: 64-s 16-s 16-s 16-s 16-s ;
The point is, word names like 4-s are quite common in Forth. To the uninitiated, they look like bizarre hieroglyphics. Even an APL programmer would be swimming in utter uncertainty. But when viewed in context, its intention is patently clear, even if you didn't know what ." itself did.
Another example is displaying the ASCII chart on the screen:
Code: Select all
: .. '. EMIT ;
: c DUP 33 < IF .. ELSE DUP EMIT THEN SPACE 1+ ;
: r c c c c c c c c c c c c c c c c CR ;
: 16r r r r r r r r r r r r r r r r r ;
: heading ." ASCII Chart" CR ;
: ascii-chart heading 64-s 0 16r DROP ;
Also, because 4-s, r, and c are not likely to be used anywhere else after the definition of 64-s or ascii-chart, I am free to redefine these words as I please, if I want. In a game situation, this may be something where I might want to compute the difference of 4 minus 's', for some idea of what 's' is at the time, I might honestly use 4-s to make it happen. If I want a word to represent the right-hand movement of a ship, I might call it r. Etc.
(Note: In this way, also, Forth fully supports the ideals of modular programming as well, including hiding of internal implementation details. As long as you don't redefine the public symbols of 'a module,' you're OK.)
Obligatory disclaimer: in NO WAY am I slandering other programming languages by revealing these "33rd degree Masonic Forth philosophy keys" as some of Forth's strongest points. I am a HUGE fan of the Oberon programming language, for example, which is the diametric opposite of everything Forth stands for (extremely rigid type checking, fully qualified Module.Object naming conventions, rigidly defined symbol visibility flags between modules, etc). But for this purpose, and indeed for most of my coding needs, Forth fills my requirements best.
Forth's small footprint and excellent speed of development is well documented elsewhere, not only here on this forum but also on the web in general. Much of the argumentation supporting APL and its descendent languages applies equally to Forth as well, for the same reasons (as do much of the argumentation in opposition to it too).
For a unique insight into how experienced Forth coders work with Forth, check out the Thinking Forth book, now online at SourceForge ( http://thinking-forth.sourceforge.net/ ). Strongly recommended reading, even if you choose to never use Forth.
- GARTHWILSON
- Forum Moderator
- Posts: 8774
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Forth would be a great way to go, and I would heartily recommend it except that at this point, I don't know of a good way for a newcomer to get into it on the 6502. I learned it on an unrelated machine, and then got a 6502 metacompiler which ran on the PC. This metacompiler is no longer available and had some very serious bugs too. I use Forth regularly on my workbench computer, but any new re-compiles of the kernel itself in EPROM are going to have to come from converting the source code to an assembly source so I can break free of this PC metacompiler whose limits I have exceeded. If I could put the time into it, I would like to get a complete 6502 Forth going that would be a turnkey solution, so the beginner would not be faced with the backwards situation of having to learn the innards before he can make anything work at all.
Bvold, if you can find someone who already has a Forth port to a machine you have there to work with, whether Apple II or whatever, that would probably be the easiest way to get started. I believe Wally Daniels actually typed the whole Fig-Forth source code listing into a PC and made the necessary changes for it to be successfully assembled by a common assembler that runs on the PC-- maybe the 2500AD assembler. (I should add that once you have even a small kernel, you can essentially be free of the PC-run assembler. Then you can mix assembly-optimized portions of code in with the higher-level code by using Forth's onboard assembler if desired.)
What Samuel said above is good, but as he said, may seem like hyroglyphics to the beginner. Leo Brodie's very dated but still excellent book "Starting Forth" is available online now at http://home.iae.nl/users/mhx/sf.html . The pictures aren't quite as entertaining as the original ones, but it should be an enjoyable read anyway. His later book "Thinking Forth", which Samuel recommended, is also excellent, although it's not so much about learning Forth so much as programming philosophy in general (which is why it may be good even for programmers who are not particularly using Forth).
Forth is an excellent performer, is very memory-thrifty, and gives the programmer an unparalleled measure of freedom. (What other language do you know of that lets the programmer modify and extend even the compiler itself?!) You may find yourself developing working, bug-free software far faster than you ever thought possible. Beware though-- the freedom not only makes good programmers better, but also makes bad ones worse.
Even after I started using Forth, I remained skeptical about how certain things could be done. But as I've gained experience, I've found that Forth actually offers very elegant solutions to things I had earlier thought were out of its reach.
Bvold, if you can find someone who already has a Forth port to a machine you have there to work with, whether Apple II or whatever, that would probably be the easiest way to get started. I believe Wally Daniels actually typed the whole Fig-Forth source code listing into a PC and made the necessary changes for it to be successfully assembled by a common assembler that runs on the PC-- maybe the 2500AD assembler. (I should add that once you have even a small kernel, you can essentially be free of the PC-run assembler. Then you can mix assembly-optimized portions of code in with the higher-level code by using Forth's onboard assembler if desired.)
What Samuel said above is good, but as he said, may seem like hyroglyphics to the beginner. Leo Brodie's very dated but still excellent book "Starting Forth" is available online now at http://home.iae.nl/users/mhx/sf.html . The pictures aren't quite as entertaining as the original ones, but it should be an enjoyable read anyway. His later book "Thinking Forth", which Samuel recommended, is also excellent, although it's not so much about learning Forth so much as programming philosophy in general (which is why it may be good even for programmers who are not particularly using Forth).
Forth is an excellent performer, is very memory-thrifty, and gives the programmer an unparalleled measure of freedom. (What other language do you know of that lets the programmer modify and extend even the compiler itself?!) You may find yourself developing working, bug-free software far faster than you ever thought possible. Beware though-- the freedom not only makes good programmers better, but also makes bad ones worse.
Even after I started using Forth, I remained skeptical about how certain things could be done. But as I've gained experience, I've found that Forth actually offers very elegant solutions to things I had earlier thought were out of its reach.
Last edited by GARTHWILSON on Thu Dec 29, 2005 12:15 am, edited 1 time in total.
- GARTHWILSON
- Forum Moderator
- Posts: 8774
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
Since you also mentioned assembly as a possibility to write your game program, I would recommend getting to know your assembler really well if you haven't already. I'm thinking especially of macro capabilities, since these let you effectively raise the level of the language, without imposing the runtime penalties. IOW, you're still laying down assembly-language instructions and you still have full control; but you're allowing the assembler to do some of your work for you (exactly the way you've told it) so that you can make faster progress and have source code that's more concise, readable, intuitive, reliable, and maintainable. I mentioned macro usage in my Tip of the Day #38 at http://www.6502.org/forum/viewtopic.php?t=342&start=30 from the old Delphi forum.
One thing I have not done yet with macros but am about to try is using them to make program structures, possibly including ones we use in Forth:
DO...LOOP (similar to FOR...NEXT in BASIC)
BEGIN...UNTIL
BEGIN...WHILE...REPEAT
IF...THEN
IF...ELSE...THEN
CASE...OF...ENDOF... ENDCASE
and so on. (BTW, Forth does let you make your own structures too.) Doing these in assembly definitely pushes the limits of my C32 assembler's macro capabilities, but I have some ideas for how to pull it off. Edit, years later: I did it, and wrote it up, at http://wilsonminesco.com/StructureMacros/ .
A language without program structures requires use of gobs of branching and jump instructions, which only results in spaghetti code. Even if you feel you have a handle on it at the time you write it, you may have a very hard time figuring it out later when a bug shows up and you have to go back and fix it.
One thing I have not done yet with macros but am about to try is using them to make program structures, possibly including ones we use in Forth:
DO...LOOP (similar to FOR...NEXT in BASIC)
BEGIN...UNTIL
BEGIN...WHILE...REPEAT
IF...THEN
IF...ELSE...THEN
CASE...OF...ENDOF... ENDCASE
and so on. (BTW, Forth does let you make your own structures too.) Doing these in assembly definitely pushes the limits of my C32 assembler's macro capabilities, but I have some ideas for how to pull it off. Edit, years later: I did it, and wrote it up, at http://wilsonminesco.com/StructureMacros/ .
A language without program structures requires use of gobs of branching and jump instructions, which only results in spaghetti code. Even if you feel you have a handle on it at the time you write it, you may have a very hard time figuring it out later when a bug shows up and you have to go back and fix it.
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?
- BitWise
- In Memoriam
- Posts: 996
- Joined: 02 Mar 2004
- Location: Berkshire, UK
- Contact:
Forth is great language for developing applications, including games but when I tried it a couple of years ago on a simple GameBoy game I found that the standard threaded code approach was too slow. In the end I wrote a Forth to assembler compiler (in Java) and used that instead.
Recently I've been playing with a Machine forth compiler for the 6502. Its still a bit simple but your welcome to have a copy. Its Java based so you can run it on almost any platform.
Heres my GBC code to show you whats possible.
Recently I've been playing with a Machine forth compiler for the 6502. Its still a bit simple but your welcome to have a copy. Its Java based so you can run it on almost any platform.
Heres my GBC code to show you whats possible.
Code: Select all
\ ---------------------------------------------------------------------------
\ A Simple PokerDice Game
\ ---------------------------------------------------------------------------
\ Copyright (C)2003 Andrew Jacobs.
\ All rights reserved.
\ ---------------------------------------------------------------------------
INCLUDE GBC.4TH \ Import support code for a Gameboy Color
INCLUDE MBC5.4TH \ ... and an MBC5 cartridge
INCLUDE SGB.4TH
\ ---------------------------------------------------------------------------
\ Imported Graphics Data
\ ---------------------------------------------------------------------------
DECIMAL
IMPORT _BorderSet AS BORDER$SET
IMPORT _BorderMap AS BORDER$MAP
IMPORT _DiceTiles AS DICE$TILESET \ 128 Tiles
IMPORT _TextTiles AS TEXT$TILESET \ 128 Tiles
IMPORT _ObjsTiles AS OBJS$TILESET \ 64 Tiles
IMPORT _DicePalettes AS DICE$PALETTES
IMPORT _ObjsPalettes AS OBJS$PALETTES
16 CONSTANT BYTES$PER$TILE
128 2* BYTES$PER$TILE * CONSTANT TILESET$SIZE
16 2* BYTES$PER$TILE * CONSTANT OBJSSET$SIZE
\ Starting Tile numbers
56 2* CONSTANT ROLL$TILE
59 2* CONSTANT SLASH$TILE
60 2* CONSTANT ZERO$TILE \ First digit tile number
192 CONSTANT DICE$EDGE
208 CONSTANT ROLL$EDGE
\ Must be the same in both the dice and text tilesets
127 2* CONSTANT PLAIN$TILE
\ ---------------------------------------------------------------------------
\ Variables
\ ---------------------------------------------------------------------------
DECIMAL
5 CARRAY UserDice \ The users dice
5 CARRAY UserRoll \ Flags for which to re-roll
5 CARRAY GameDice \ The games dice
5 CARRAY GameRoll \ Flags for which to re-roll
VARIABLE Won \ Number of games won
VARIABLE Lost \ Number of games lost
\ ---------------------------------------------------------------------------
\ Utility Functions
\ ---------------------------------------------------------------------------
: 4+ ( n -- n+4 )
2+ 2+
;
\ ---------------------------------------------------------------------------
\ Random Number Functions
\ ---------------------------------------------------------------------------
VARIABLE SEED \ Random number seed
\ Uses a simple linear congruental method to create a new random number based
\ on the seed value.
: Random ( -- n )
SEED @ 73 * 1- DUP SEED !
;
\ Returns a value (1-6) representing the roll of a dice. Note that two bits
\ are dropped from the initial random number and UM/MOD is used to perform
\ the modulus faster.
: Throw ( -- n )
Random 2/ 2/ 0 6 UM/MOD DROP 1+
;
\ ---------------------------------------------------------------------------
\ Video Buffer
\ ---------------------------------------------------------------------------
HEX
D000 CONSTANT VB \ Video buffer in WRAM Bank 0 CHR and 1 ATR
DECIMAL
: SelectChrBuffer ( -- )
1 WRAM$SetBank
;
: SelectAtrBuffer ( -- )
2 WRAM$SetBank
;
\ ---------------------------------------------------------------------------
\ Interrupt Code
\ ---------------------------------------------------------------------------
HCVARIABLE VBlankFlag \ Flag set during V-Blank period
HCVARIABLE UpdateFlag \ Flags for GDMA transfer actions
BIT0 CONSTANT UPDATE$CHR
BIT1 CONSTANT UPDATE$ATR
\ Called at the start of the VBLANK period this routine sets a semaphore used
\ for synchronisation and manages GDMA transfers to the video RAM.
: VBlankHandler ( -- )
1 VBlankFlag C!
UpdateFlag C@ ?DUP IF
DUP
UPDATE$CHR AND IF
UPDATE$CHR XOR
WRAM$GetBank
SelectChrBuffer
Video$SetChrBank
ELSE
UPDATE$ATR XOR
WRAM$GetBank
SelectAtrBuffer
Video$SetAtrBank
THEN
1024 BG1 VB GDMA
WRAM$SetBank
UpdateFlag C!
THEN
7 SEED +!
;
\ Causes the processor to enter the low power start until the next VBLANK
\ period begins.
: WaitForVBlank ( -- )
0 VBlankFlag C!
BEGIN HALT VBlankFlag C@ UNTIL
;
: SetUpdateFlag ( flags -- )
DI
UpdateFlag C@ OR
UpdateFlag C!
EI
;
\ ---------------------------------------------------------------------------
\ Tile Setting
\ ---------------------------------------------------------------------------
\ A die image is comprised of 16 tiles with consecutive numbers. The tile
\ designer writes them out in the following order.
\
\ +----+----+----+----+
\ | +0 | +2 | +8 | +A | This code takes a recursive approach to setting the
\ +----+----+----+----+ tile data. The 32x32 image is set as four 16x16
\ | +1 | +3 | +9 | +B | images each of which is in turn set as two 8x16
\ +----+----+----+----+ sub-images.
\ | +4 | +6 | +C | +E |
\ +----+----+----+----+ These routines do not take into account any screen
\ | +5 | +7 | +D | +F | wrap around.
\ +----+----+----+----+
: SetTileData8x16 ( tile addr -- )
OVER 1+ OVER 32 + C!
C!
;
: SetTileData16x16 ( tile addr -- )
OVER 2+ OVER 1+ SetTileData8x16
SetTileData8x16
;
: SetTileData32x32 ( tile addr -- )
OVER 12 + OVER 66 + SetTileData16x16
OVER 8 + OVER 2 + SetTileData16x16
OVER 4 + OVER 64 + SetTileData16x16
SetTileData16x16
;
\ ---------------------------------------------------------------------------
\ Palette Setting
\ ---------------------------------------------------------------------------
\ Palette data for the die images needs to be set in a similar way to the
\ tiles, however the pallete value is the same for all the constitutent tiles
\ (as we don't have any horz/vert flipping to worry about).
0 CONSTANT USER$BLACK \ Palette for a user dice with black graphics
1 CONSTANT USER$BLUE \ ... and so on
2 CONSTANT USER$RED
3 CONSTANT USER$GREEN
4 CONSTANT GAME$BLACK \ Palette for a game dice with black graphics
5 CONSTANT GAME$BLUE \ ... and so on
6 CONSTANT GAME$RED
7 CONSTANT GAME$GREEN
: SetTilePalette8x16 ( pal addr -- )
OVER OVER 32 + C!
C!
;
: SetTilePalette16x16 ( pal addr -- )
OVER OVER 1+ SetTilePalette8x16
SetTilePalette8x16
;
: SetTilePalette32x32 ( pal addr -- )
OVER OVER 66 + SetTilePalette16x16
OVER OVER 2 + SetTilePalette16x16
OVER OVER 64 + SetTilePalette16x16
SetTilePalette16x16
;
\ ---------------------------------------------------------------------------
\ Sprite Functions
\ ---------------------------------------------------------------------------
: ?Sprites ( -- flag )
LCDC C@ LCDC$OBJ$ON AND
;
: SpritesToggle ( -- )
LCDC C@ LCDC$OBJ$ON XOR LCDC C!
;
: SpritesOn ( -- )
?Sprites 0= IF SpritesToggle THEN
;
: SpritesOff ( -- )
?Sprites 0= NOT IF SpritesToggle THEN
;
: ClearSprites ( -- )
OAM [ 8 4 * ] 0 FILL
;
: SelectDice ( n -- )
ClearSprites
1- 16* 2* DUP DUP DUP DUP DUP DUP DUP
[ 9 8 * 16 + ] [ OAM 0 + ] C!
[ 0 8 * 8 + ] + [ OAM 1 + ] C!
[ DICE$EDGE 0 2* + ] [ OAM 2 + ] C!
0 [ OAM 3 + ] C!
[ 9 8 * 16 + ] [ OAM 4 + ] C!
[ 1 8 * 8 + ] + [ OAM 5 + ] C!
[ DICE$EDGE 1 2* + ] [ OAM 6 + ] C!
0 [ OAM 7 + ] C!
[ 9 8 * 16 + ] [ OAM 8 + ] C!
[ 2 8 * 8 + ] + [ OAM 9 + ] C!
[ DICE$EDGE 4 2* + ] [ OAM 10 + ] C!
0 [ OAM 11 + ] C!
[ 9 8 * 16 + ] [ OAM 12 + ] C!
[ 3 8 * 8 + ] + [ OAM 13 + ] C!
[ DICE$EDGE 5 2* + ] [ OAM 14 + ] C!
0 [ OAM 15 + ] C!
[ 11 8 * 16 + ] [ OAM 16 + ] C!
[ 0 8 * 8 + ] + [ OAM 17 + ] C!
[ DICE$EDGE 2 2* + ] [ OAM 18 + ] C!
0 [ OAM 19 + ] C!
[ 11 8 * 16 + ] [ OAM 20 + ] C!
[ 1 8 * 8 + ] + [ OAM 21 + ] C!
[ DICE$EDGE 3 2* + ] [ OAM 22 + ] C!
0 [ OAM 23 + ] C!
[ 11 8 * 16 + ] [ OAM 24 + ] C!
[ 2 8 * 8 + ] + [ OAM 25 + ] C!
[ DICE$EDGE 6 2* + ] [ OAM 26 + ] C!
0 [ OAM 27 + ] C!
[ 11 8 * 16 + ] [ OAM 28 + ] C!
[ 3 8 * 8 + ] + [ OAM 29 + ] C!
[ DICE$EDGE 7 2* + ] [ OAM 30 + ] C!
0 [ OAM 31 + ] C!
;
: SelectRoll ( -- )
ClearSprites
[ 16 8 * 16 + ] [ OAM 0 + ] C!
[ 0 8 * 16 + ] [ OAM 1 + ] C!
[ ROLL$EDGE 0 2* + ] [ OAM 2 + ] C!
0 [ OAM 3 + ] C!
[ 16 8 * 16 + ] [ OAM 4 + ] C!
[ 1 8 * 16 + ] [ OAM 5 + ] C!
[ ROLL$EDGE 1 2* + ] [ OAM 6 + ] C!
0 [ OAM 7 + ] C!
[ 16 8 * 16 + ] [ OAM 8 + ] C!
[ 2 8 * 16 + ] [ OAM 9 + ] C!
[ ROLL$EDGE 2 2* + ] [ OAM 10 + ] C!
0 [ OAM 11 + ] C!
;
\ ---------------------------------------------------------------------------
\ Hand Scoring
\ ---------------------------------------------------------------------------
HEX
0100 CONSTANT HIGH$CARD
0200 CONSTANT PAIR
0300 CONSTANT TWO$PAIRS
0400 CONSTANT THREE$OF$KIND
0500 CONSTANT FULL$HOUSE
0600 CONSTANT FOUR$OF$KIND
0700 CONSTANT FIVE$OF$KIND
0F00 CONSTANT RANK$MASK
000F CONSTANT DIE$MASK
DECIMAL
7 CARRAY Frequency \ An array of dice frequencies
\ Sets the counts in the frequency count array to all zeros.
: ClearFrequency ( -- )
Frequency 7 0 FILL
;
\ Fills the frequency array with the number of each die in the given hand.
\ Note that dice are numbered 1 to 6 (0 is a dice back!).
: CountFrequency ( hand -- )
5 0 DO
DUP I + C@
Frequency + DUP C@
1+ SWAP C!
LOOP
DROP
;
: >Score ( rank high low -- score )
SWAP 16* OR OR
;
: ?FiveOfKind ( -- false / score true )
0
7 1 DO
Frequency I + C@ 5 = IF
DROP
FIVE$OF$KIND 1 0 >Score 1
LEAVE
THEN
LOOP
;
: ?FourOfKind ( -- false / score true )
0
7 1 DO
Frequency I + C@ 4 = IF
DROP
FOUR$OF$KIND I 0 >Score 1
LEAVE
THEN
LOOP
;
: ?FullHouse ( -- false / score true )
0
7 1 DO
Frequency I + C@ 3 = IF
7 1 DO
I J <> IF
Frequency I + C@ 2 = IF
DROP
FULL$HOUSE J I >Score 1
LEAVE
THEN
THEN
LOOP
LEAVE
THEN
LOOP
;
: ?ThreeOfKind ( -- false / score true )
0
7 1 DO
Frequency I + C@ 3 = IF
DROP
THREE$OF$KIND I 0 >Score 1
LEAVE
THEN
LOOP
;
: ?TwoPairs ( -- false / score true )
0
6 1 DO
Frequency I + C@ 2 = IF
7 I 1+ DO
Frequency I + C@ 2 = IF
DROP
TWO$PAIRS I J >Score 1
LEAVE
THEN
LOOP
LEAVE
THEN
LOOP
;
: ?Pair ( -- false / score true )
0
7 1 DO
Frequency I + C@ 2 = IF
DROP
PAIR I 0 >Score 1
LEAVE
THEN
LOOP
;
: ScoreHand ( hand -- score )
ClearFrequency
CountFrequency
?FiveOfKind 0= IF
?FourOfKind 0= IF
?FullHouse 0= IF
?ThreeOfKind 0= IF
?TwoPairs 0= IF
?Pair 0= IF
Frequency 6 + C@ IF
HIGH$CARD 6 0 >Score
ELSE
HIGH$CARD 5 0 >Score
THEN
THEN
THEN
THEN
THEN
THEN
THEN
;
: Score>Rank ( score -- rank )
RANK$MASK AND
;
: Score>HighDie ( score -- die )
16/ DIE$MASK AND
;
: Score>LowDie ( score -- die )
DIE$MASK AND
;
\ ---------------------------------------------------------------------------
\ Strategy Routines
\ ---------------------------------------------------------------------------
\ Mark any dice in the computers hand that are not either of the specified
\ values for re-rolling.
: RollAllBut ( value value -- )
5 0 DO
OVER GameDice I + C@ <> IF
DUP GameDice I + C@ <> IF
1 GameRoll I + C!
THEN
THEN
LOOP
DROP DROP
;
: RollAllButHighDie ( score -- )
Score>HighDie 0 RollAllBut
;
: HighestDie ( -- value )
0
5 0 DO
GameDice I + C@ MAX
LOOP
;
: RollWildly ( game user -- game user )
OVER Score>Rank
DUP HIGH$CARD = IF
DROP OVER Score>HighDie OVER Score>HighDie > IF
OVER RollAllButHighDie
ELSE
HighestDie 0 RollAllBut
THEN
ELSE
PAIR = IF
OVER Score>HighDie HighestDie RollAllBut
ELSE
OVER RollAllButHighDie
THEN
THEN
;
: RollAggressively ( game user -- )
DUP Score>Rank
DUP HIGH$CARD = IF
DROP 4 5 RollAllBut
ELSE
DUP FOUR$OF$KIND = IF
DROP DUP Score>HighDie HighestDie > IF
0 0 RollAllBut
ELSE
HighestDie 0 RollAllBut
THEN
ELSE
FIVE$OF$KIND = IF
DROP DUP Score>HighDie HighestDie > IF
0 0 RollAllBut
ELSE
HighestDie 0 RollAllBut
THEN
ELSE
RollWildly
THEN
THEN
THEN
;
\ Works out which dice can safely be rolled to improve a hand.
: RollDefensively ( game user -- )
OVER Score>Rank
DUP HIGH$CARD = IF
DROP OVER RollAllButHighDie
ELSE
DUP PAIR = IF
DROP OVER RollAllButHighDie
ELSE
DUP TWO$PAIRS = IF
DROP OVER DUP Score>HighDie SWAP Score>LowDie RollAllBut
ELSE
DUP THREE$OF$KIND = IF
DROP OVER RollAllButHighDie
ELSE
FOUR$OF$KIND = IF
OVER RollAllButHighDie
THEN
THEN
THEN
THEN
THEN
;
: DetermineGameRolls ( game user -- )
OVER OVER < IF
RollAggressively
ELSE
RollDefensively
THEN
DROP DROP
;
\ ---------------------------------------------------------------------------
\ Text Display Functions
\ ---------------------------------------------------------------------------
: ShowText ( addr tile count width -- )
OVER - >R
\ WaitForVBlank
\ Copy over the text tile
OVER + SWAP DO
DUP 1+ SWAP I 2* SWAP
SetTileData8x16
LOOP
\ Erase the rest of the space
R> 0 DO
DUP 1+ SWAP
PLAIN$TILE SWAP
SetTileData8x16
LOOP
DROP
UPDATE$CHR SetUpdateFlag
;
\ ---------------------------------------------------------------------------
\ Score Name Display
\ ---------------------------------------------------------------------------
0 CONSTANT TEXT$ACE$HIGH
6 CONSTANT TEXT$KING$HIGH
12 CONSTANT TEXT$PAIR$NINES
20 CONSTANT TEXT$PAIR$TENS
28 CONSTANT TEXT$PAIR$JACKS
37 CONSTANT TEXT$PAIR$QUEENS
46 CONSTANT TEXT$PAIR$KINGS
54 CONSTANT TEXT$PAIR$ACES
62 CONSTANT TEXT$TWO$PAIRS
68 CONSTANT TEXT$THREE$KIND
78 CONSTANT TEXT$FULL$HOUSE
85 CONSTANT TEXT$FOUR$KIND
94 CONSTANT TEXT$FIVE$KIND
102 CONSTANT TEXT$NAME$END
: ?DescribeHighCard ( addr score -- addr score 0/addr tile count 1 )
DUP Score>Rank HIGH$CARD = IF
Score>HighDie 6 = IF
TEXT$ACE$HIGH [ TEXT$KING$HIGH TEXT$ACE$HIGH - ]
ELSE
TEXT$KING$HIGH [ TEXT$PAIR$NINES TEXT$KING$HIGH - ]
THEN
1
ELSE
0
THEN
;
CREATE PairNameTable
TEXT$PAIR$NINES C,
TEXT$PAIR$TENS C,
TEXT$PAIR$JACKS C,
TEXT$PAIR$QUEENS C,
TEXT$PAIR$KINGS C,
TEXT$PAIR$ACES C,
TEXT$TWO$PAIRS C,
: ?DescribePair ( addr score -- addr score 0/addr tile count 1 )
DUP Score>Rank PAIR = IF
Score>HighDie 1- ' PairNameTable + DUP
C@ SWAP 1+ C@ OVER - 1
ELSE
0
THEN
;
: ?DescribeTwoPairs ( addr score -- addr score 0/addr tile count 1 )
DUP Score>Rank TWO$PAIRS = IF
DROP TEXT$TWO$PAIRS [ TEXT$THREE$KIND TEXT$TWO$PAIRS - ] 1
ELSE
0
THEN
;
: ?DescribeThreeOfKind ( addr score -- addr score 0/addr tile count 1 )
DUP Score>Rank THREE$OF$KIND = IF
DROP TEXT$THREE$KIND [ TEXT$FULL$HOUSE TEXT$THREE$KIND - ] 1
ELSE
0
THEN
;
: ?DescribeFullHouse ( addr score -- addr score 0/addr tile count 1 )
DUP Score>Rank FULL$HOUSE = IF
DROP TEXT$FULL$HOUSE [ TEXT$FOUR$KIND TEXT$FULL$HOUSE - ] 1
ELSE
0
THEN
;
: ?DescribeFourOfKind ( addr score -- addr score 0/addr tile count 1 )
DUP Score>Rank FOUR$OF$KIND = IF
DROP TEXT$FOUR$KIND [ TEXT$FIVE$KIND TEXT$FOUR$KIND - ] 1
ELSE
0
THEN
;
: Describe ( addr score -- )
?DescribeHighCard 0= IF
?DescribePair 0= IF
?DescribeTwoPairs 0= IF
?DescribeThreeOfKind 0= IF
?DescribeFullHouse 0= IF
?DescribeFourOfKind 0= IF
DROP TEXT$FIVE$KIND [ TEXT$NAME$END TEXT$FIVE$KIND - ]
THEN
THEN
THEN
THEN
THEN
THEN
18 ShowText
;
\ ---------------------------------------------------------------------------
\ Won / Lost Display
\ ---------------------------------------------------------------------------
: FactorNumber ( n -- 100's 10's 1's )
100 /MOD SWAP 10 /MOD SWAP
;
: Digit>Tile ( n -- tile )
2* ZERO$TILE +
;
: ShowScores ( -- )
Won @ FactorNumber
Lost @ FactorNumber
\ WaitForVBlank
Digit>Tile [ VB 16 32 * + 18 + ] SetTileData8x16
Digit>Tile [ VB 16 32 * + 17 + ] SetTileData8x16
Digit>Tile [ VB 16 32 * + 16 + ] SetTileData8x16
Digit>Tile [ VB 16 32 * + 14 + ] SetTileData8x16
Digit>Tile [ VB 16 32 * + 13 + ] SetTileData8x16
Digit>Tile [ VB 16 32 * + 12 + ] SetTileData8x16
[ UPDATE$CHR UPDATE$ATR OR ] SetUpdateFlag
;
\ ---------------------------------------------------------------------------
\
\ ---------------------------------------------------------------------------
CVARIABLE Counter
\ Periodically turns the sprites on or off to make the current selection
\ flash.
: ToggleSelection
Counter C@ 1+
DUP 20 = IF
SpritesToggle
DROP 0
THEN
Counter C!
;
\ Waits for all the joypad keys to be released. Periodically the sprites are
\ toggled to make the selection flash.
: WaitForRelease ( -- )
BEGIN
WaitForVBlank
ToggleSelection
Joypad$Read 0= UNTIL
;
: WaitForPress ( -- keys )
BEGIN
WaitForVBlank
ToggleSelection
Joypad$Read ?DUP UNTIL
;
: ShowSelection ( value -- )
?DUP IF
SelectDice
ELSE
SelectRoll
THEN
;
: ?MoveLeft ( flag value key -- flag value key 0/flag value 0 1 )
DUP Joypad$LEFT = IF
DROP OVER IF
1- DUP -1 = IF
DROP 5
THEN
THEN
0 1
ELSE
0
THEN
;
: ?MoveRight ( flag value key -- flag value key 0/flag value 0 1 )
DUP Joypad$RIGHT = IF
DROP OVER IF
1+ DUP 6 = IF
DROP 0
THEN
THEN
0 1
ELSE
0
THEN
;
: GetSelection ( flag value -- value )
0 Counter C!
BEGIN
WaitForVBlank
DUP ShowSelection
WaitForRelease
WaitForPress
?MoveLeft 0= IF
?MoveRight 0= IF
THEN
THEN
Joypad$A = UNTIL
SpritesOff
SWAP DROP
;
\ ---------------------------------------------------------------------------
\ Game
\ ---------------------------------------------------------------------------
: Value>Tile ( value -- tile )
16*
;
CREATE PaletteMap
USER$BLACK C,
USER$BLACK C,
USER$RED C,
USER$BLUE C,
USER$GREEN C,
USER$RED C,
USER$BLACK C,
: Value>Palette ( value -- palette )
' PaletteMap + C@
;
: DrawGameDie ( value pos -- )
4* [ 1 32 * VB + ] + SWAP ( addr value )
SelectAtrBuffer
OVER OVER Value>Palette 4+ SWAP SetTilePalette32x32
SelectChrBuffer
Value>Tile SWAP SetTileData32x32
[ UPDATE$CHR UPDATE$ATR OR ] SetUpdateFlag
;
: DrawUserDie ( value pos -- )
4* [ 9 32 * VB + ] + SWAP ( addr value )
SelectAtrBuffer
OVER OVER Value>Palette SWAP SetTilePalette32x32
SelectChrBuffer
Value>Tile SWAP SetTileData32x32
[ UPDATE$CHR UPDATE$ATR OR ] SetUpdateFlag
;
: DrawGameScreen ( -- )
Video$Off
\ Install the palettes
DICE$PALETTES ROM$SetBank 8 0 Video$SetChrPalettes
OBJS$PALETTES ROM$SetBank 1 0 Video$SetObjPalettes
\ Install the tile sets
Video$SetAtrBank
TEXT$TILESET ROM$SetBank CHR TILESET$SIZE CMOVE
Video$SetChrBank
DICE$TILESET ROM$SetBank CHR TILESET$SIZE CMOVE
OBJS$TILESET ROM$SetBank
[ CHR TILESET$SIZE + 64 BYTES$PER$TILE * - ] OBJSSET$SIZE CMOVE
\ Initialise the Video Buffer
SelectAtrBuffer
VB [ 32 32 * ] USER$BLACK FILL
VB [ 8 32 * ] GAME$BLACK FILL
[ VB 5 32 * + ] [ 32 2* ] GAME$BLACK BIT3 OR FILL
[ VB 13 32 * + ] [ 32 2* ] USER$BLACK BIT3 OR FILL
SelectChrBuffer
VB [ 32 32 * ] PLAIN$TILE FILL
\ Draw the initial dice
5 0 DO
0 I DrawGameDie
0 I DrawUserDie
LOOP
\ Draw the Roll Button
[ VB 16 32 * + 1+ ]
3 0 DO
ROLL$TILE I 2* +
OVER I +
SetTileData8x16
LOOP DROP
SLASH$TILE [ VB 16 32 * + 15 + ] SetTileData8x16
\ Copy the buffer to the screens
Video$SetAtrBank SelectAtrBuffer
VB BG1 1024 CMOVE
Video$SetChrBank SelectChrBuffer
VB BG1 1024 CMOVE
OAM 160 0 FILL
[ LCDC$ON LCDC$BG$8000 OR LCDC$BG$9800 OR LCDC$OBJ$16 OR LCDC$BG$ON OR ] LCDC C!
;
: RollDice ( -- )
5 0 DO
GameRoll I + C@ IF
Throw DUP
GameDice I + C!
\ WaitForVBlank
I DrawGameDie
THEN
UserRoll I + C@ IF
Throw DUP
UserDice I + C!
\ WaitForVBlank
I DrawUserDie
THEN
[ UPDATE$CHR UPDATE$ATR OR ] SetUpdateFlag
WaitForVBlank
WaitForVBlank
LOOP
;
: RollDiceAndScore ( -- gamescore userscore )
8 0 DO
I 3 > IF
Joypad$Read Joypad$A = IF
LEAVE
THEN
THEN
RollDice
LOOP
GameDice ScoreHand
[ VB 5 32 * + 1+ ] OVER Describe
UserDice ScoreHand
[ VB 13 32 * + 1+ ] OVER Describe
GameRoll 5 0 FILL
UserRoll 5 0 FILL
UPDATE$CHR SetUpdateFlag
;
70 CONSTANT TEXT$DRAW
75 CONSTANT TEXT$WIN
80 CONSTANT TEXT$LOSE
86 CONSTANT TEXT$2ROLLS
90 CONSTANT TEXT$1ROLL
94 CONSTANT TEXT$END
: UpdateScore ( game user -- )
OVER OVER = IF
\ A Draw
DROP DROP
[ VB 16 32 * + 5 + ]
TEXT$DRAW [ TEXT$WIN TEXT$DRAW - ]
ELSE
< IF
1 Won +!
[ VB 16 32 * + 5 + ]
TEXT$WIN [ TEXT$LOSE TEXT$WIN - ]
ELSE
1 Lost +!
[ VB 16 32 * + 5 + ]
TEXT$LOSE [ TEXT$2ROLLS TEXT$LOSE - ]
THEN
THEN
7 ShowText
;
: PickDiceToReroll ( -- )
0
BEGIN
1 SWAP GetSelection
DUP 0> IF
DUP 1- >R
R@ UserRoll +
DUP C@ 1 XOR
DUP IF
0
ELSE
R@ UserDice + C@
THEN
R> DrawUserDie
SWAP C!
THEN
DUP 0= UNTIL
DROP
;
: ShowRemainingRolls ( flag -- )
[ VB 16 32 * + 5 + ] SWAP
IF
TEXT$2ROLLS [ TEXT$1ROLL TEXT$2ROLLS - ]
ELSE
TEXT$1ROLL [ TEXT$END TEXT$1ROLL - ]
THEN
7 ShowText
[ UPDATE$CHR ] SetUpdateFlag
;
: PlayGame ( -- )
DrawGameScreen
BEGIN
ShowScores
GameRoll 5 1 FILL
UserRoll 5 1 FILL
0 0 GetSelection DROP
\ [ BG1 16 32 * + 5 + ]
\ TEXT$END 0 ShowText
RollDiceAndScore
2 0 DO
DetermineGameRolls
I 0= ShowRemainingRolls
PickDiceToReroll
RollDiceAndScore
LOOP
UpdateScore
AGAIN
;
\ ---------------------------------------------------------------------------
\ Credits
\ ---------------------------------------------------------------------------
: ShowCredits ( -- )
Video$Off
;
\ ---------------------------------------------------------------------------
\ Entry Point
\ ---------------------------------------------------------------------------
: Initialise ( -- )
0 SEED !
0 UpdateFlag C!
0 Won !
0 Lost !
;
: DrawBorder ( -- )
BORDER$MAP ROM$SetBank BORDER$SET DROP SGB$Border
;
: StartHere ( -- )
Video$Off
Initialise
DrawBorder
' VBlankHandler VBLANK !
IE C@ IE$VBLANK OR IE C! EI
ShowCredits
PlayGame
;Andrew Jacobs
6502 & PIC Stuff - http://www.obelisk.me.uk/
Cross-Platform 6502/65C02/65816 Macro Assembler - http://www.obelisk.me.uk/dev65/
Open Source Projects - https://github.com/andrew-jacobs
6502 & PIC Stuff - http://www.obelisk.me.uk/
Cross-Platform 6502/65C02/65816 Macro Assembler - http://www.obelisk.me.uk/dev65/
Open Source Projects - https://github.com/andrew-jacobs
BitWise wrote:
Forth is great language for developing applications, including games but when I tried it a couple of years ago on a simple GameBoy game I found that the standard threaded code approach was too slow.
Also remember that many Forth environments do support an internal assembler for the system on which it's targeting. That allows you to recode time-critical functions in pure assembly language if desired, without breaking out of the Forth environment. As long as your assembly-coded words conform to the ABI of the Forth environment, it will be virtually indistinguishable from a normal colon definition.
Quote:
Recently I've been playing with a Machine forth compiler for the 6502. Its still a bit simple but your welcome to have a copy. Its Java based so you can run it on almost any platform.
The above-listed Forth source code, by the way, is a great example of the "new" way (say, post-1985) to code in Forth. Lots of longer symbol names, and use of indentation to mnemonically keep track of nesting level. I haven't seen the use of $ as a word-separator character since the days of VMS, honestly. Why did you choose $ instead of - or _? I'm just curious.
I personally stick to the older style of coding, with some modern touches. One definition, one-to-two lines rule, and as a result, my source code tends to look more traditional or, if space allows it, more like Smalltalk source code.
As with any programming language, however, sticking with a consistent coding convention is important for maintenance of the code down the road. There is no "One True Convention" with any programming language, let alone with Forth.
- BitWise
- In Memoriam
- Posts: 996
- Joined: 02 Mar 2004
- Location: Berkshire, UK
- Contact:
Quote:
Was this implementation a direct- or indirect-threaded system?
Code: Select all
ShowCredits: ; : ShowCredits
JP Video_Off ; Video$Off
Initialise: ; : Initialise
CALL _ZERO ; 0
CALL SEED ; SEED
CALL _STORE ; !
CALL _ZERO ; 0
CALL UpdateFlag ; UpdateFlag
CALL _CSTORE ; C!
CALL _ZERO ; 0
CALL Won ; Won
CALL _STORE ; !
CALL _ZERO ; 0
CALL Lost ; Lost
JP _STORE ; !
DrawBorder: ; : DrawBorder
CALL BORDER_MAP ; BORDER$MAP
CALL ROM_SetBank ; ROM$SetBank
CALL BORDER_SET ; BORDER$SET
LD C,[HL] ; DROP
INC HL
LD B,[HL]
INC HL
JP SGB_Border ; SGB$Border
StartHere: ; : StartHere
CALL Video_Off ; Video$Off
CALL Initialise ; Initialise
CALL DrawBorder ; DrawBorder
RST _PUSHBC ; ' VBlankHandler
LD BC,VBlankHandler
CALL _VBLANK ; VBLANK
CALL _STORE ; !
CALL IE ; IE
CALL _CLOAD ; C@
CALL _ONE ; IE$VBLANK
CALL _OR ; OR
CALL IE ; IE
CALL _CSTORE ; C!
EI ; EI
CALL ShowCredits ; ShowCredits
JP PlayGame ; PlayGame
BOOT: JP StartHere ; Entry PointQuote:
Also remember that many Forth environments do support an internal assembler for the system on which it's targeting. That allows you to recode time-critical functions in pure assembly language if desired, without breaking out of the Forth environment. As long as your assembly-coded words conform to the ABI of the Forth environment, it will be virtually indistinguishable from a normal colon definition.
Quote:
I haven't seen the use of $ as a word-separator character since the days of VMS, honestly. Why did you choose $ instead of - or _? I'm just curious.
Quote:
I was considering a MachineForth environment for the 65816 and later gave up on it, as even the most basic primitive I could find couldn't map to any less than three CPU instructions.
The 6502 compiler is still a work in progress. Currently it translates this
Code: Select all
\ Bootstrap Standard Forth Words
: @ A! @A ;
: ! A! !A ;
: +! A! @A + !A ;
: 0 0 ;
: 1 1 ;
: 2 2 ;
\ Stack functions
: 2DUP DUP DUP ;
: 2DROP DROP DROP ;
\ Arithmetic
: 1+ 1 + ;
: 1- 1 - ;
\ Comparisons
: 0= IF 0 ELSE 1 THEN SWAP DROP ;Code: Select all
; ======================================================
; System Variables
; ------------------------------------------------------
.ORG $00
_ADR: .DS 2 ; Address register
_TOS: .DS 2 ; Top of stack
_STK: .DS 64 ; Data Stack
; ======================================================
; Entry Point
; ------------------------------------------------------
.ORG $8000
.START $8000
LDX #$FF
TXS
LDX #64
JMP (LASTWORD)
; ======================================================
; Runtime Support Functions
; ------------------------------------------------------
_PLUS: CLC
LDA _TOS+0
ADC _STK+0,X
STA _TOS+0
LDA _TOS+1
ADC _STK+1,X
STA _TOS+1
INX
INX
RTS
_TWO_STAR: ASL _TOS+0
ROL _TOS+1
RTS
_TWO_SLASH: LDA _TOS+1
ASL
ROR _TOS+1
ROR _TOS+0
RTS
_AND: LDA _TOS+0
AND _STK+0,X
STA _TOS+0
LDA _TOS+1
AND _STK+1,X
STA _TOS+1
INX
INX
RTS
_DUP: DEX
DEX
LDA _TOS+0
STA _STK+0,X
LDA _TOS+1
STA _STK+1,X
RTS
_ASTORE: LDA _TOS+0
STA _ADR+0
LDA _TOS+1
STA _ADR+1
_DROP: LDA _STK+0,X
STA _TOS+0
LDA _STK+1,X
STA _TOS+1
_NIP: INX
INX
RTS
_NOT: LDA _TOS+0
EOR #$FF
STA _TOS+0
LDA _TOS+1
EOR #$FF
STA _TOS+1
RTS
_OVER: JSR _DUP
LDA _STK+2,X
LDY _STK+3,X
__LITERAL_: STA _TOS+0
STY _TOS+1
RTS
_SWAP: LDY _STK+0,X
LDA _TOS+0
STY _TOS+0
STA _STK+0,X
LDY _STK+1,X
LDA _TOS+1
STY _TOS+1
STA _STK+1,X
RTS
_XOR: LDA _TOS+0
EOR _STK+0,X
STA _TOS+0
LDA _TOS+1
EOR _STK+1,X
STA _TOS+1
INX
INX
RTS
_A: JSR _DUP
LDA _ADR+0
STA _TOS+0
LDA _ADR+1
STA _TOS+1
RTS
_LOAD_A: JSR _DUP
LDY #0
LDA (_ADR),Y
STA _TOS+0
INY
LDA (_ADR),Y
STA _TOS+1
RTS
_LOAD_APLUS: JSR _LOAD_A
CLC
TYA
ADC _ADR+0
BCC *+2
INC _ADR+1
RTS
_STORE_A: LDY #0
LDA _TOS+0
STA (_ADR),Y
INY
LDA _TOS+1
STA (_ADR),Y
JMP _DROP
_STORE_APLUS: JSR _STORE_A
CLC
TYA
ADC _ADR+0
BCC *+2
INC _ADR+1
RTS
; ======================================================
; Compiled Words
; ------------------------------------------------------
_LOAD: JSR _ASTORE ; : @ A!
JMP _LOAD_A ; @A ;
_STORE: JSR _ASTORE ; : ! A!
JMP _STORE_A ; !A ;
_PLUS_STORE: JSR _ASTORE ; : +! A!
JSR _LOAD_A ; @A
JSR _PLUS ; +
JMP _STORE_A ; !A ;
_ZERO: JSR _DUP ; : 0 DUP
LDA #<0 ; 0
LDY #>0
JMP __LITERAL_ ; (LITERAL) ;
_ONE: JSR _DUP ; : 1 DUP
LDA #<1 ; 1
LDY #>1
JMP __LITERAL_ ; (LITERAL) ;
_TWO: JSR _DUP ; : 2 DUP
LDA #<2 ; 2
LDY #>2
JMP __LITERAL_ ; (LITERAL) ;
_TWO_DUP: JSR _DUP ; : 2DUP DUP
JMP _DUP ; DUP ;
_TWO_DROP: JSR _DROP ; : 2DROP DROP
JMP _DROP ; DROP ;
_ONE_PLUS: JSR _ONE ; : 1+ 1
JMP _PLUS ; + ;
_ONE_MINUS: JSR _ONE ; : 1- 1
JSR _DUP ; DUP
LDA #<0 ; -
LDY #>0
JMP __LITERAL_ ; (LITERAL) ;
_ZERO_EQUALS: LDA _TOS+0 ; : 0= IF
ORA _TOS+1
BNE __J2
JMP __J1
__J2: JSR _ZERO ; 0
JMP __J3 ; ELSE
__J1: JSR _ONE ; 1
__J3: JSR _SWAP ; THEN SWAP
JMP _DROP ; DROP ;
LASTWORD: .DW _ZERO_EQUALS
Andrew Jacobs
6502 & PIC Stuff - http://www.obelisk.me.uk/
Cross-Platform 6502/65C02/65816 Macro Assembler - http://www.obelisk.me.uk/dev65/
Open Source Projects - https://github.com/andrew-jacobs
6502 & PIC Stuff - http://www.obelisk.me.uk/
Cross-Platform 6502/65C02/65816 Macro Assembler - http://www.obelisk.me.uk/dev65/
Open Source Projects - https://github.com/andrew-jacobs
- GARTHWILSON
- Forum Moderator
- Posts: 8774
- Joined: 30 Aug 2002
- Location: Southern California
- Contact:
> IIRC, Garth's Forth implementation is direct-threaded, is it not?
> What is the performance like on that system?
It's indirect-threaded, but has hundreds of primitives. A lot of words that are normally colon definitions are primitives instead, which reduces the number of times NEXT has to run to get a job done. It's also easy to write new primitives, ISRs, subroutines, or runtimes with the onboard assembler, so anything that's time-critical easily gets the streamlining it needs. As Samuel hinted, you can try an idea quickly in high-level Forth, and then re-write the word as a primitive (ie, in assembly) to speed it up, without having to change any of the code that uses it. Of course in Forth, assembling and running a new piece of assembly code happens almost instantaneously. I've even changed an ISR on the fly while it was getting called tens of thousands of times per second. (To do that, you assemble the new ISR, and then between interrupts, change the vector to point to the new ISR.)
> I personally stick to the older style of coding, with some
> modern touches.
I suppose everyone has their own style, and others' styles kind of rub us the wrong way. That's one thing however that makes Forth so well suited for us free-thinkers-- we can form the language the way we want it (although that will improve as we gain experience and get ideas from others). Many things in the game above could be made more concise and memory-efficient, although sometimes I just punch some code out fast and not optimize if it's something I'll use once and then discard.
> so I rewrote the compiler to generate optimised assembler in
> stead, like this
What you have there is basically subroutine-threaded Forth, except that you don't appear to have any headers for the system to be able to find the words itself in order to do its own compiling. After Bruce's write-up last year on how subroutine-threaded can be more time- and memory-efficient than you'd think, I've been tempted to try it. I'd also like to implement Samuel's dictionary-chacheing for quicker compilation.
> What is the performance like on that system?
It's indirect-threaded, but has hundreds of primitives. A lot of words that are normally colon definitions are primitives instead, which reduces the number of times NEXT has to run to get a job done. It's also easy to write new primitives, ISRs, subroutines, or runtimes with the onboard assembler, so anything that's time-critical easily gets the streamlining it needs. As Samuel hinted, you can try an idea quickly in high-level Forth, and then re-write the word as a primitive (ie, in assembly) to speed it up, without having to change any of the code that uses it. Of course in Forth, assembling and running a new piece of assembly code happens almost instantaneously. I've even changed an ISR on the fly while it was getting called tens of thousands of times per second. (To do that, you assemble the new ISR, and then between interrupts, change the vector to point to the new ISR.)
> I personally stick to the older style of coding, with some
> modern touches.
I suppose everyone has their own style, and others' styles kind of rub us the wrong way. That's one thing however that makes Forth so well suited for us free-thinkers-- we can form the language the way we want it (although that will improve as we gain experience and get ideas from others). Many things in the game above could be made more concise and memory-efficient, although sometimes I just punch some code out fast and not optimize if it's something I'll use once and then discard.
> so I rewrote the compiler to generate optimised assembler in
> stead, like this
What you have there is basically subroutine-threaded Forth, except that you don't appear to have any headers for the system to be able to find the words itself in order to do its own compiling. After Bruce's write-up last year on how subroutine-threaded can be more time- and memory-efficient than you'd think, I've been tempted to try it. I'd also like to implement Samuel's dictionary-chacheing for quicker compilation.
Getting back to the original topic, what to use depends on what type of game it is. An action/arcade style game with good (by Apple II or C64 standards) graphics and sound is going to wind up mostly (if not entirely) in assembly. A compiler (e.g. C) may or may not be able to generate code that is fast enough. Something that stops and waits for user input (a card game like cribbage, e.g.) where speed isn't such an issue can be written in BASIC. BASIC games have historically varied greatly in terms of quality, but there have been a surprising large number of very good games written in BASIC, even action games that incorporate a (software) game timer. Action games are possible in something like BASIC when they use text rather than graphics, e.g. or "skiing/driving" type games that use text scrolling. The classic Eamon text adventure series for the Apple II was (if memory serves) written in BASIC. Many "textris" (Tetris with text characters) implementations have been written in high level languages.
Forth is also a good choice, for the reasons mentioned by others. (By the way, it's usually relatively easy to write a Forth assembler -- even for processors with complex instruction sets -- if your system doesn't have one.)
For the most part, you can use whatever language you wish. You can always use assembly for those sections where greater speed is needed. I've written a number of (short) games in a variety of programming languages, and to me, having a good library and good tools is more helpful than the choice of language.
Forth is also a good choice, for the reasons mentioned by others. (By the way, it's usually relatively easy to write a Forth assembler -- even for processors with complex instruction sets -- if your system doesn't have one.)
For the most part, you can use whatever language you wish. You can always use assembly for those sections where greater speed is needed. I've written a number of (short) games in a variety of programming languages, and to me, having a good library and good tools is more helpful than the choice of language.
Other excellent choices would be Pascal or its children. Though, I have to admit, I am not a fan of Pascal itself -- I much prefer its descendents. I've never used Modula-2, but I have used Oberon, which is a direct (and vastly simplified) dialect of Modula-2. I rank Oberon right up there with Forth as far as my all-time favorite programming language is concerned. I often have substantial difficulties selecting between Forth or Oberon for coding a very large project with.
Another possibility would be to write your own compiler as well. It looks as though BCPL would be pretty easy to port, and from what I've seen of it, is remarkably powerful. In fact, there is an entire command-line operating system called TriPOS written in BCPL, which you can freely download and play with. TriPOS is also the principle ancestor of AmigaOS as well, thanks to Commodore's decision to use an off-the-shelf DOS implementation for AmigaOS.
I've seen some programs written in PL/M before, though mostly for the 8080, 8085, and 80286 CPUs; that also looks like a pretty nice language to play with in many respects. However, I'd think that PL/M is a bit harder to use overall, being a subset of PL/I. And, while PL/I contains most (if not all?) of the features of many other modern programming languages, actually using these features leaves much to be desired (defining a data structure, for example, seems pretty baroque to me compared to C or Oberon). Though, I do like how PL/I and PL/M handle pointers in contrast to C. Also, PL/I is the ancestor of yet another extremely great (and rediculously hard to port) language: REXX.
I don't know -- Forth, BASIC, Oberon, Modula-2, PL/M, C, BCPL -- there are so many to choose from, especially if you're willing to port the language yourself. BCPL is certainly the easiest "traditional" language to port. Oberon doesn't seem too hard to implement yourself from scratch, but it does look as though porting Oberon takes, according to the book titled Project Oberon, about 6 man-months of time to complete (pretty fast all things considered, but still, who wants to hack on a language for 6 months? I certainly don't). Modula-2 is best left to the professional language implementors, since it's much more complicated than Oberon. BASIC compilers and PL/M compilers fall into the same category as far as I'm concerned, because each pretty much defines its keywords pretty much arbitrarily and has very similar overall structure. And, let's face it, PL/I (and by extension, PL/M variants that support the feature) supports very BASIC-like string manipulation features. In fact, IIRC, BASIC got its string functions *from* PL/I to begin with.
Another possibility would be to write your own compiler as well. It looks as though BCPL would be pretty easy to port, and from what I've seen of it, is remarkably powerful. In fact, there is an entire command-line operating system called TriPOS written in BCPL, which you can freely download and play with. TriPOS is also the principle ancestor of AmigaOS as well, thanks to Commodore's decision to use an off-the-shelf DOS implementation for AmigaOS.
I've seen some programs written in PL/M before, though mostly for the 8080, 8085, and 80286 CPUs; that also looks like a pretty nice language to play with in many respects. However, I'd think that PL/M is a bit harder to use overall, being a subset of PL/I. And, while PL/I contains most (if not all?) of the features of many other modern programming languages, actually using these features leaves much to be desired (defining a data structure, for example, seems pretty baroque to me compared to C or Oberon). Though, I do like how PL/I and PL/M handle pointers in contrast to C. Also, PL/I is the ancestor of yet another extremely great (and rediculously hard to port) language: REXX.
I don't know -- Forth, BASIC, Oberon, Modula-2, PL/M, C, BCPL -- there are so many to choose from, especially if you're willing to port the language yourself. BCPL is certainly the easiest "traditional" language to port. Oberon doesn't seem too hard to implement yourself from scratch, but it does look as though porting Oberon takes, according to the book titled Project Oberon, about 6 man-months of time to complete (pretty fast all things considered, but still, who wants to hack on a language for 6 months? I certainly don't). Modula-2 is best left to the professional language implementors, since it's much more complicated than Oberon. BASIC compilers and PL/M compilers fall into the same category as far as I'm concerned, because each pretty much defines its keywords pretty much arbitrarily and has very similar overall structure. And, let's face it, PL/I (and by extension, PL/M variants that support the feature) supports very BASIC-like string manipulation features. In fact, IIRC, BASIC got its string functions *from* PL/I to begin with.
-
Nightmaretony
- In Memoriam
- Posts: 618
- Joined: 27 Jun 2003
- Location: Meadowbrook
- Contact:
For an arcade style game, the 6502 works out nicely. One fo the fastest arcade games in thew world that I know of called Tempest uses a 1 MHz 6502 inside. Almost all Atari games up until Gauntlet used the 6502.
One way of thinking which I use in my arcade design is what Apple calls an event loop. Here it is quite simplified for conceptual. I am using it right now for programming a pinball machine (yup, am STILL on that project, gang)
SETUP
Init variables
LOOP
Get inputs (read the ports)
Process inputs (into memory variables)
Logical processing (game and output rules)
Push outputs (screen, sound, etc)
Goto LOOP
(Personal aside here)
On the pinball loop, it runs either 30 or 60 times a second, I forget which offhand. I actually do the get inputs and outputs simultaneously on the pinball because it requires strobing a matrix like mad, so the loop is more like:
LOOP
Matrix = 1
Get Inputs
Push Outputs
Increment Matrix
Matrix = 2
Get Inputs
Push Outputs
Increment Matrix
and so on up to 7. The reason I dont use a logical is because of the unique switches and lights at each matrix point So on the simplifiy, it is:
Loop
Inputs/Outputs
Game Logic
Goto Loop
So far, it is looking good. The matrix loop is ocmpeltely written and the display part seems to work fantastic. I had written up a nice attract mode display show animation set which works like a champ in realtime.
Had just compeltely rewrote thhe display routine from the ground up. I had wanted to add flashing capability at any speed, but it was proving to be kludgy so it got a rewrite. It is inline code like everything in this program, so it gets called to init and show on the display, and it counts down the length of time the message or animation shows, and uses a XOR setup to strobe the display off and on at a set speed. In fact, for whatever it is worth. NOTE: This part of the routine isnt tested as of yet. I still have to redo the animation tables in order to test it out...
if it helps anyone with ideas, party on
; And now, the message center for displaying messages on the score display.
; The main flag to be checked is the MessageCenterStatusFlag
; 00 = do nothing, skip routine
; 01 = load in message with the InitMessageDisplay routine
; 02 = message status running. This is on while you are displaying the desired message
; 03 = End the message (instantly, as an over-ride)
; the display will clear depending on the MessageClearAtEnd flag.
; 00 = do not clear display
; 01 or more, clear display
; as a rule, this flag should hang out at 00 all the time except when showing a message or animation.
MessageCenter
LDA MessageCenterStatusFlag
BEQ MessageCenterSkip ; Just skip the entire message center if it is 00.
CMP #$01 ; Check to see if I need to init and load a message
BEQ MessageCenterInit
CMP #$02 ; Is the message current and showing?
BEQ MessageCenterShowJump
CMP #$03 ; EndMessageDisplay?
BEQ MessageCenterEndJump
MessageCenterSkip
JMP MainSwitchBoard
MessageCenterShowJump
JMP MessageCenterShow
MessageCenterEndJump
JMP MessageCenterEnd
; This is the InitMessageDisplay. Get things all set up.
MessageCenterInit
LDA MessageCenter1
STA Display1
EOR #$FF
STA Display1Mask
LDA MessageCenter2
STA Display2
EOR #$ff
STA Display2Mask
LDA MessageCenter3
STA Display3
EOR #$ff
STA Display3Mask
LDA MessageCenter4
STA Display4
EOR #$ff
STA Display4Mask
LDA MessageCenter5
STA Display5
EOR #$ff
STA Display5Mask
LDA MessageCenter6
STA Display6
EOR #$ff
STA Display6Mask
LDA #$02
STA MessageCenterStatusFlag
JMP MainSwitchBoard
; This is the show routine which is called 60 times a second.
MessageCenterShow
LDA MessageCenterTimerFlag ; Main countdown. At 0, it jumps to end of routine.
CMP #$00
BEQ MessageCenterEnd
DEC MessageCenterTimerFlag
LDA MessageCenterFlickerRateFlagTemp ; count down the flicker rate to flash the display
CMP #$00
BEQ MessageCenterShowToggle
DEC MessageCenterFlickerRateFlagTemp
JMP MainSwitchBoard
MessageCenterShowToggle
LDA Display1 ; This clever routine uses the mask to change what is inside of it.
EOR #$ff
AND Display1Mask
STA Display1
LDA Display2
EOR #$ff
AND Display2Mask
STA Display2
LDA Display3
EOR #$ff
AND Display3Mask
STA Display3
LDA Display4
EOR #$ff
AND Display4Mask
STA Display4
LDA Display5
EOR #$ff
AND Display5Mask
STA Display5
LDA Display6
EOR #$ff
AND Display6Mask
STA Display6
LDA MessageCenterFlickerRateFlag ; Restore flicker rate
STA MessageCenterFlickerRateFlagTemp
JMP MainSwitchBoard
; Here are the end clearing routines.
MessageCenterEnd
LDA MessageClearAtEnd ; 0 is dont clear, 1 or more, clear display
BEQ MessageCenterEndNoClearDisplay
LDA #$00 ; this clears out the main display
STA Display1
STA Display2
STA Display3
STA Display4
STA Display5
STA Display6
MessageCenterEndNoClearDisplay
LDA #$00
STA MessageCenterStatusFlag
STA MessageCenterTimerFlag
STA MessageCenter1
STA MessageCenter2
STA MessageCenter3
STA MessageCenter4
STA MessageCenter5
STA MessageCenter6
STA Display1Mask
STA Display2Mask
STA Display3Mask
STA Display4Mask
STA Display5Mask
STA Display6Mask
One way of thinking which I use in my arcade design is what Apple calls an event loop. Here it is quite simplified for conceptual. I am using it right now for programming a pinball machine (yup, am STILL on that project, gang)
SETUP
Init variables
LOOP
Get inputs (read the ports)
Process inputs (into memory variables)
Logical processing (game and output rules)
Push outputs (screen, sound, etc)
Goto LOOP
(Personal aside here)
On the pinball loop, it runs either 30 or 60 times a second, I forget which offhand. I actually do the get inputs and outputs simultaneously on the pinball because it requires strobing a matrix like mad, so the loop is more like:
LOOP
Matrix = 1
Get Inputs
Push Outputs
Increment Matrix
Matrix = 2
Get Inputs
Push Outputs
Increment Matrix
and so on up to 7. The reason I dont use a logical is because of the unique switches and lights at each matrix point So on the simplifiy, it is:
Loop
Inputs/Outputs
Game Logic
Goto Loop
So far, it is looking good. The matrix loop is ocmpeltely written and the display part seems to work fantastic. I had written up a nice attract mode display show animation set which works like a champ in realtime.
Had just compeltely rewrote thhe display routine from the ground up. I had wanted to add flashing capability at any speed, but it was proving to be kludgy so it got a rewrite. It is inline code like everything in this program, so it gets called to init and show on the display, and it counts down the length of time the message or animation shows, and uses a XOR setup to strobe the display off and on at a set speed. In fact, for whatever it is worth. NOTE: This part of the routine isnt tested as of yet. I still have to redo the animation tables in order to test it out...
if it helps anyone with ideas, party on
; And now, the message center for displaying messages on the score display.
; The main flag to be checked is the MessageCenterStatusFlag
; 00 = do nothing, skip routine
; 01 = load in message with the InitMessageDisplay routine
; 02 = message status running. This is on while you are displaying the desired message
; 03 = End the message (instantly, as an over-ride)
; the display will clear depending on the MessageClearAtEnd flag.
; 00 = do not clear display
; 01 or more, clear display
; as a rule, this flag should hang out at 00 all the time except when showing a message or animation.
MessageCenter
LDA MessageCenterStatusFlag
BEQ MessageCenterSkip ; Just skip the entire message center if it is 00.
CMP #$01 ; Check to see if I need to init and load a message
BEQ MessageCenterInit
CMP #$02 ; Is the message current and showing?
BEQ MessageCenterShowJump
CMP #$03 ; EndMessageDisplay?
BEQ MessageCenterEndJump
MessageCenterSkip
JMP MainSwitchBoard
MessageCenterShowJump
JMP MessageCenterShow
MessageCenterEndJump
JMP MessageCenterEnd
; This is the InitMessageDisplay. Get things all set up.
MessageCenterInit
LDA MessageCenter1
STA Display1
EOR #$FF
STA Display1Mask
LDA MessageCenter2
STA Display2
EOR #$ff
STA Display2Mask
LDA MessageCenter3
STA Display3
EOR #$ff
STA Display3Mask
LDA MessageCenter4
STA Display4
EOR #$ff
STA Display4Mask
LDA MessageCenter5
STA Display5
EOR #$ff
STA Display5Mask
LDA MessageCenter6
STA Display6
EOR #$ff
STA Display6Mask
LDA #$02
STA MessageCenterStatusFlag
JMP MainSwitchBoard
; This is the show routine which is called 60 times a second.
MessageCenterShow
LDA MessageCenterTimerFlag ; Main countdown. At 0, it jumps to end of routine.
CMP #$00
BEQ MessageCenterEnd
DEC MessageCenterTimerFlag
LDA MessageCenterFlickerRateFlagTemp ; count down the flicker rate to flash the display
CMP #$00
BEQ MessageCenterShowToggle
DEC MessageCenterFlickerRateFlagTemp
JMP MainSwitchBoard
MessageCenterShowToggle
LDA Display1 ; This clever routine uses the mask to change what is inside of it.
EOR #$ff
AND Display1Mask
STA Display1
LDA Display2
EOR #$ff
AND Display2Mask
STA Display2
LDA Display3
EOR #$ff
AND Display3Mask
STA Display3
LDA Display4
EOR #$ff
AND Display4Mask
STA Display4
LDA Display5
EOR #$ff
AND Display5Mask
STA Display5
LDA Display6
EOR #$ff
AND Display6Mask
STA Display6
LDA MessageCenterFlickerRateFlag ; Restore flicker rate
STA MessageCenterFlickerRateFlagTemp
JMP MainSwitchBoard
; Here are the end clearing routines.
MessageCenterEnd
LDA MessageClearAtEnd ; 0 is dont clear, 1 or more, clear display
BEQ MessageCenterEndNoClearDisplay
LDA #$00 ; this clears out the main display
STA Display1
STA Display2
STA Display3
STA Display4
STA Display5
STA Display6
MessageCenterEndNoClearDisplay
LDA #$00
STA MessageCenterStatusFlag
STA MessageCenterTimerFlag
STA MessageCenter1
STA MessageCenter2
STA MessageCenter3
STA MessageCenter4
STA MessageCenter5
STA MessageCenter6
STA Display1Mask
STA Display2Mask
STA Display3Mask
STA Display4Mask
STA Display5Mask
STA Display6Mask
"My biggest dream in life? Building black plywood Habitrails"
In last September I've written a Puzzle game in Forth (on an 6502 based Atari 8bit).
I found the game for Linux and MacOS X on the Internet (Stroq Homepage: http://stroq.sourceforge.net/) and decided to do a conversation for the Atari 8Bit Computer in Forth.
Winfried Piegsda did the graphical design, sound and artwork, I did the coding in Forth (X-Forth for Atari).
The Game was fone in little less than 4 weeks to be submitted to the annual ABBUC Software Contest where it scored the 5th place.
For most parts Forth on the 6502 is fast enough, but critical sections needs to be done in Assembler. But having an Assembler "build into" Forth, that is not a big problem
.
You can see the sourcecode and download a disk image for an Atari800 Emulator at http://www.strotmann.de/twiki/bin/view/ ... troqSource
Carsten
I found the game for Linux and MacOS X on the Internet (Stroq Homepage: http://stroq.sourceforge.net/) and decided to do a conversation for the Atari 8Bit Computer in Forth.
Winfried Piegsda did the graphical design, sound and artwork, I did the coding in Forth (X-Forth for Atari).
The Game was fone in little less than 4 weeks to be submitted to the annual ABBUC Software Contest where it scored the 5th place.
For most parts Forth on the 6502 is fast enough, but critical sections needs to be done in Assembler. But having an Assembler "build into" Forth, that is not a big problem
You can see the sourcecode and download a disk image for an Atari800 Emulator at http://www.strotmann.de/twiki/bin/view/ ... troqSource
Carsten