6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Thu Nov 14, 2024 5:19 am

All times are UTC




Post new topic Reply to topic  [ 12 posts ] 
Author Message
 Post subject: Writing a Game
PostPosted: Wed Dec 28, 2005 5:08 pm 
Offline

Joined: Thu Jul 07, 2005 12:34 am
Posts: 23
Location: Minnesota
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? :twisted:


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Dec 28, 2005 6:33 pm 
Offline

Joined: Sat Jan 04, 2003 10:03 pm
Posts: 1706
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:

Code:
: 4-s ." ----" ;
: 16-s 4-s 4-s 4-s 4-s ;
: 64-s 16-s 16-s 16-s 16-s ;


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:

Code:
: .. '. 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 ;


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.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Dec 28, 2005 9:07 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8540
Location: Southern California
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.


Last edited by GARTHWILSON on Thu Dec 29, 2005 12:15 am, edited 1 time in total.

Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Wed Dec 28, 2005 9:42 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8540
Location: Southern California
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.

_________________
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?


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Thu Dec 29, 2005 12:58 pm 
Offline
User avatar

Joined: Tue Mar 02, 2004 8:55 am
Posts: 996
Location: Berkshire, UK
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.
Code:
\ ---------------------------------------------------------------------------
\ 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


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Thu Dec 29, 2005 3:42 pm 
Offline

Joined: Sat Jan 04, 2003 10:03 pm
Posts: 1706
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.


Was this implementation a direct- or indirect-threaded system? I find indirect threading to be very CPU bandwidth wasteful in general. Many people swear by it, but I've always used direct-threaded implementations, and it's always been very fast for me. GForth under Linux on an AMD Athlon Slot (yes, Slot) A system runs about 1/4th to 1/8th the speed of optimized C when used in direct-threaded mode. IIRC, Garth's Forth implementation is direct-threaded, is it not? What is the performance like on that system? I suppose also we should also find out what the hardware specifications on your GameBoy is, to serve as a basis for comparison.

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.


Interesting. 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 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. :) I almost never have more than one IF or BEGIN block in any single definition. And, as with other programming rules, rules can be broken when well justified. I will use long word names typically for symbols which are "external" or "global" in scope, and shorter names for internal symbols.

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. :)


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Thu Dec 29, 2005 7:05 pm 
Offline
User avatar

Joined: Tue Mar 02, 2004 8:55 am
Posts: 996
Location: Berkshire, UK
Quote:
Was this implementation a direct- or indirect-threaded system?

The original implementation was based on a FIG Forth for the Z80/8080. I think it was indirect threaded. As the GameBoy is basically an 4 MHz 8080 (so about the same as a 1 Mhz 6502) with extensions it seemed like a reasonable starting point. A lot of the processing has to be done during the screen fly back period on the GameBoy and I found that the interpreted version exceeded the time for this period so I rewrote the compiler to generate optimised assembler in stead, like this:
Code:
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 Point

Quote:
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.

My compiler does not provide a complete Forth environment, just enough to translate the Forth for a standalone application into assembler. In the GB version I added IMPORT .. AS .. construct to allow you to reference code in other modules. The 6502 one currently doesn't do this.
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.

I tend to write 'object oriented' assembler and I was looking for a way to make related functions and symbols have a standard pattern. I decided to use '$' to separate the 'class' from the member. This also keeps '-' and '_' available for member names. Most assemblers don't like $'s in names so the translator exchanges the $ for an _ on the generated code (I think I'll allow it in my 65xx family assembler). The 6502 compiler expands symbols into words (*/ => STAR_SLASH) more systematically.
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.

I was/am looking for a way to minimise the size of the runtime library but without crippling the execution perfornace. MF uses a minimal set of primitives based on Charles Moore's RISC processors. I thought I'd start with those and see how well it works (The same approach he used with ColorForth).
The 6502 compiler is still a work in progress. Currently it translates this
Code:
\ 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 ;

into this
Code:
; ======================================================
; 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


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Thu Dec 29, 2005 9:45 pm 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8540
Location: Southern California
> 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.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Fri Dec 30, 2005 11:41 pm 
Offline
User avatar

Joined: Thu Mar 11, 2004 7:42 am
Posts: 362
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.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sat Dec 31, 2005 12:27 am 
Offline

Joined: Sat Jan 04, 2003 10:03 pm
Posts: 1706
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.


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Sat Dec 31, 2005 4:15 pm 
Offline

Joined: Fri Jun 27, 2003 8:12 am
Posts: 618
Location: Meadowbrook
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

_________________
"My biggest dream in life? Building black plywood Habitrails"


Top
 Profile  
Reply with quote  
 Post subject:
PostPosted: Thu Jan 26, 2006 6:40 pm 
Offline

Joined: Wed May 21, 2003 1:08 pm
Posts: 27
Location: Germany
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


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 12 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 1 guest


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to: