diff --git a/autotest/autotest.bat b/autotest/autotest.bat index fa07d1d..b9792a5 100644 --- a/autotest/autotest.bat +++ b/autotest/autotest.bat @@ -163,6 +163,12 @@ exit /b %errorlevel% ..\release\oscar64 -e -O0 -n %~1 @if %errorlevel% neq 0 goto :error +..\release\oscar64 -e -Os %~1 +@if %errorlevel% neq 0 goto :error + +..\release\oscar64 -e -Os -n %~1 +@if %errorlevel% neq 0 goto :error + ..\release\oscar64 -e -O3 %~1 @if %errorlevel% neq 0 goto :error @@ -181,6 +187,9 @@ exit /b %errorlevel% ..\release\oscar64 -e -O0 %~1 @if %errorlevel% neq 0 goto :error +..\release\oscar64 -e -Os %~1 +@if %errorlevel% neq 0 goto :error + ..\release\oscar64 -e -O3 %~1 @if %errorlevel% neq 0 goto :error diff --git a/oscar64/InterCode.cpp b/oscar64/InterCode.cpp index ef65a19..fe11613 100644 --- a/oscar64/InterCode.cpp +++ b/oscar64/InterCode.cpp @@ -412,6 +412,30 @@ static bool SameMem(const InterOperand& op1, const InterOperand& op2) } } +static bool SameMemRegion(const InterOperand& op1, const InterOperand& op2) +{ + if (op1.mMemory != op2.mMemory) + return false; + + switch (op1.mMemory) + { + case IM_LOCAL: + case IM_FPARAM: + case IM_PARAM: + case IM_FRAME: + case IM_FFRAME: + return true; + case IM_ABSOLUTE: + return true; + case IM_GLOBAL: + return op1.mLinkerObject == op2.mLinkerObject; + case IM_INDIRECT: + return op1.mTemp == op2.mTemp; + default: + return false; + } +} + // returns true if op2 is part of op1 static bool SameMemSegment(const InterOperand& op1, const InterOperand& op2) { @@ -8426,7 +8450,7 @@ void InterCodeBasicBlock::PeepholeOptimization(void) if (i != j) mInstructions[j] = ins; } - else if (mInstructions[i]->mCode == IC_LEA && mInstructions[i]->mSrc[0].mTemp == -1) + else if (mInstructions[i]->mCode == IC_LEA && (mInstructions[i]->mSrc[0].mTemp < 0 || mInstructions[i]->mSrc[1].mTemp < 0)) { InterInstruction* ins(mInstructions[i]); int j = i; @@ -8747,6 +8771,33 @@ void InterCodeBasicBlock::PeepholeOptimization(void) } while (changed); + // sort stores up + + do + { + changed = false; + + for (int i = 0; i + 1 < mInstructions.Size(); i++) + { + if (mInstructions[i + 0]->mCode == IC_STORE && mInstructions[i + 1]->mCode == IC_STORE && + !mInstructions[i + 0]->mVolatile && !mInstructions[i + 1]->mVolatile && +// !CollidingMem(mInstructions[i + 0]->mSrc[1], mInstructions[i + 1]->mSrc[1]) && + SameMemRegion(mInstructions[i + 0]->mSrc[1], mInstructions[i + 1]->mSrc[1]) && + + (mInstructions[i + 0]->mSrc[1].mVarIndex > mInstructions[i + 1]->mSrc[1].mVarIndex || + mInstructions[i + 0]->mSrc[1].mVarIndex == mInstructions[i + 1]->mSrc[1].mVarIndex && + mInstructions[i + 0]->mSrc[1].mIntConst > mInstructions[i + 1]->mSrc[1].mIntConst)) + { + InterInstruction* ins = mInstructions[i + 1]; + mInstructions[i + 1] = mInstructions[i + 0]; + mInstructions[i + 0] = ins; + changed = true; + } + } + + } while (changed); + + if (mTrueJump) mTrueJump->PeepholeOptimization(); if (mFalseJump) mFalseJump->PeepholeOptimization(); } @@ -9746,6 +9797,21 @@ void InterCodeProcedure::Close(void) } #endif +#if 1 + do { + TempForwarding(); + } while (GlobalConstantPropagation()); + + ResetVisited(); + mEntryBlock->PeepholeOptimization(); + + TempForwarding(); + RemoveUnusedInstructions(); + + DisassembleDebug("Peephole optimized"); + +#endif + MapVariables(); DisassembleDebug("mapped variabled"); diff --git a/oscar64/NativeCodeGenerator.cpp b/oscar64/NativeCodeGenerator.cpp index b069c9e..be9ff26 100644 --- a/oscar64/NativeCodeGenerator.cpp +++ b/oscar64/NativeCodeGenerator.cpp @@ -5124,8 +5124,56 @@ void NativeCodeBasicBlock::LoadValue(InterCodeProcedure* proc, const InterInstru NativeCodeBasicBlock * NativeCodeBasicBlock::CopyValue(InterCodeProcedure* proc, const InterInstruction * ins, NativeCodeProcedure* nproc) { + int size = ins->mConst.mOperandSize; + int msize = 4; + + if (nproc->mGenerator->mCompilerOptions & COPT_OPTIMIZE_AUTO_UNROLL) + msize = 8; + else if (nproc->mGenerator->mCompilerOptions & COPT_OPTIMIZE_CODE_SIZE) + msize = 2; +#if 1 + if (ins->mSrc[0].mTemp < 0 && ins->mSrc[1].mTemp < 0) + { + if (ins->mSrc[0].mMemory == IM_GLOBAL && ins->mSrc[1].mMemory == IM_FRAME) + { + int index = ins->mSrc[1].mVarIndex + ins->mSrc[1].mIntConst + 2; + + int areg = BC_REG_STACK; + CheckFrameIndex(areg, index, size); + + if (size <= msize) + { + for (int i = 0; i < size; i++) + { + mIns.Push(NativeCodeInstruction(ASMIT_LDY, ASMIM_IMMEDIATE, index + i)); + mIns.Push(NativeCodeInstruction(ASMIT_LDA, ASMIM_ABSOLUTE, ins->mSrc[0].mIntConst + i, ins->mSrc[0].mLinkerObject)); + mIns.Push(NativeCodeInstruction(ASMIT_STA, ASMIM_INDIRECT_Y, areg)); + } + + return this; + } + else + { + NativeCodeBasicBlock* lblock = nproc->AllocateBlock(); + NativeCodeBasicBlock* eblock = nproc->AllocateBlock(); + + mIns.Push(NativeCodeInstruction(ASMIT_LDY, ASMIM_IMMEDIATE, index)); + this->Close(lblock, nullptr, ASMIT_JMP); + lblock->mIns.Push(NativeCodeInstruction(ASMIT_LDA, ASMIM_ABSOLUTE_Y, ins->mSrc[0].mIntConst - index, ins->mSrc[0].mLinkerObject)); + lblock->mIns.Push(NativeCodeInstruction(ASMIT_STA, ASMIM_INDIRECT_Y, areg)); + lblock->mIns.Push(NativeCodeInstruction(ASMIT_INY, ASMIM_IMPLIED)); + lblock->mIns.Push(NativeCodeInstruction(ASMIT_CPY, ASMIM_IMMEDIATE, (index + size) & 255)); + lblock->Close(lblock, eblock, ASMIT_BNE); + + return eblock; + } + } + } +#endif + int sreg, dreg; + if (ins->mSrc[0].mTemp < 0) { if (ins->mSrc[0].mMemory == IM_GLOBAL) @@ -5251,14 +5299,6 @@ NativeCodeBasicBlock * NativeCodeBasicBlock::CopyValue(InterCodeProcedure* proc, dreg = BC_REG_TMP + proc->mTempOffset[ins->mSrc[1].mTemp]; } - int size = ins->mConst.mOperandSize; - int msize = 4; - - if (nproc->mGenerator->mCompilerOptions & COPT_OPTIMIZE_AUTO_UNROLL) - msize = 8; - else if (nproc->mGenerator->mCompilerOptions & COPT_OPTIMIZE_CODE_SIZE) - msize = 2; - if (size <= msize) { for (int i = 0; i < size; i++) @@ -14055,10 +14095,160 @@ bool NativeCodeBasicBlock::OptimizeSelect(NativeCodeProcedure* proc) return changed; } +static bool CheckBlockCopySequence(const GrowingArray& ins, int si, int n) +{ + if (si + 2 * n <= ins.Size() && + ins[si + 0].mType == ASMIT_LDA && (ins[si + 0].mMode == ASMIM_ZERO_PAGE || ins[si + 0].mMode == ASMIM_ABSOLUTE) && + ins[si + 1].mType == ASMIT_STA && (ins[si + 1].mMode == ASMIM_ZERO_PAGE || ins[si + 1].mMode == ASMIM_ABSOLUTE)) + { + for (int i = 1; i < n; i++) + { + if (!(ins[si + 2 * i + 0].mType == ASMIT_LDA && ins[si + 2 * i + 0].mMode == ins[si + 0].mMode && ins[si + 2 * i + 0].mAddress == ins[si + 0].mAddress + i && + ins[si + 2 * i + 1].mType == ASMIT_STA && ins[si + 2 * i + 1].mMode == ins[si + 1].mMode && ins[si + 2 * i + 1].mAddress == ins[si + 1].mAddress + i)) + return false; + } + if (ins[si + 2 * n - 1].mLive & (LIVE_CPU_REG_A | LIVE_CPU_REG_Z)) + return false; + + return true; + } + else + return false; +} + +bool NativeCodeBasicBlock::BlockSizeCopyReduction(NativeCodeProcedure* proc, int& si, int& di) +{ + if ((proc->mGenerator->mCompilerOptions & COPT_OPTIMIZE_CODE_SIZE)) + { + if (si + 1 < mIns.Size() && + mIns[si + 0].mType == ASMIT_LDA && (mIns[si + 0].mMode == ASMIM_ZERO_PAGE || mIns[si + 0].mMode == ASMIM_ABSOLUTE) && + mIns[si + 1].mType == ASMIT_STA && (mIns[si + 1].mMode == ASMIM_ZERO_PAGE || mIns[si + 1].mMode == ASMIM_ABSOLUTE)) + { + int i = 1; + while (si + 2 * i + 1 < mIns.Size() && + mIns[si + 2 * i + 0].mType == ASMIT_LDA && mIns[si + 2 * i + 0].mMode == mIns[si + 0].mMode && mIns[si + 2 * i + 0].mAddress == mIns[si + 0].mAddress + i && + mIns[si + 2 * i + 1].mType == ASMIT_STA && mIns[si + 2 * i + 1].mMode == mIns[si + 1].mMode && mIns[si + 2 * i + 1].mAddress == mIns[si + 1].mAddress + i && + !(mIns[si + 2 * i + 1].mLive & (LIVE_CPU_REG_A | LIVE_CPU_REG_Z))) + { + i++; + } + + if (i > 2) + { + if (!(mIns[si + 0].mLive & LIVE_CPU_REG_X)) + { + int k = 1; + while (CheckBlockCopySequence(mIns, si + 2 * k * i, i)) + k++; + + + int sz = 3 + 4 * k; + for (int j = 0; j < k; j++) + { + NativeCodeInstruction lins = mIns[si + 2 * i * j + 0]; + NativeCodeInstruction sins = mIns[si + 2 * i * j + 1]; + + if (lins.mMode == ASMIM_ZERO_PAGE) + lins.mMode = ASMIM_ZERO_PAGE_X; + else + { + lins.mMode = ASMIM_ABSOLUTE_X; + sz++; + } + if (sins.mMode == ASMIM_ZERO_PAGE) + sins.mMode = ASMIM_ZERO_PAGE_X; + else + { + sins.mMode = ASMIM_ABSOLUTE_X; + sz++; + } + + if (j == 0) + mIns[di++] = NativeCodeInstruction(ASMIT_LDX, ASMIM_IMMEDIATE, i - 1); + + mIns[di++] = lins; + mIns[di++] = sins; + } + + mIns[di++] = NativeCodeInstruction(ASMIT_DEX, ASMIM_IMPLIED); + mIns[di++] = NativeCodeInstruction(ASMIT_BPL, ASMIM_RELATIVE, -sz); + + si += 2 * i * k; + + return true; + } + } + + } + if (si + 2 < mIns.Size() && + mIns[si + 0].mType == ASMIT_LDA && (mIns[si + 0].mMode == ASMIM_ZERO_PAGE || mIns[si + 0].mMode == ASMIM_ABSOLUTE) && + mIns[si + 1].mType == ASMIT_STA && (mIns[si + 1].mMode == ASMIM_ZERO_PAGE || mIns[si + 1].mMode == ASMIM_ABSOLUTE) && + mIns[si + 1].mType == ASMIT_STA && (mIns[si + 1].mMode == ASMIM_ZERO_PAGE || mIns[si + 1].mMode == ASMIM_ABSOLUTE)) + { + int i = 1; + while (si + 3 * i + 2 < mIns.Size() && + mIns[si + 3 * i + 0].mType == ASMIT_LDA && mIns[si + 3 * i + 0].mMode == mIns[si + 0].mMode && mIns[si + 3 * i + 0].mAddress == mIns[si + 0].mAddress + i && + mIns[si + 3 * i + 1].mType == ASMIT_STA && mIns[si + 3 * i + 1].mMode == mIns[si + 1].mMode && mIns[si + 3 * i + 1].mAddress == mIns[si + 1].mAddress + i && + mIns[si + 3 * i + 2].mType == ASMIT_STA && mIns[si + 3 * i + 2].mMode == mIns[si + 2].mMode && mIns[si + 3 * i + 2].mAddress == mIns[si + 2].mAddress + i && + !(mIns[si + 3 * i + 2].mLive & (LIVE_CPU_REG_A | LIVE_CPU_REG_Z))) + { + i++; + } + + if (i > 2) + { + if (!(mIns[si + 0].mLive & LIVE_CPU_REG_X)) + { + NativeCodeInstruction lins = mIns[si + 0]; + NativeCodeInstruction sins0 = mIns[si + 1]; + NativeCodeInstruction sins1 = mIns[si + 2]; + + int sz = 9; + if (lins.mMode == ASMIM_ZERO_PAGE) + lins.mMode = ASMIM_ZERO_PAGE_X; + else + { + lins.mMode = ASMIM_ABSOLUTE_X; + sz++; + } + if (sins0.mMode == ASMIM_ZERO_PAGE) + sins0.mMode = ASMIM_ZERO_PAGE_X; + else + { + sins0.mMode = ASMIM_ABSOLUTE_X; + sz++; + } + if (sins1.mMode == ASMIM_ZERO_PAGE) + sins1.mMode = ASMIM_ZERO_PAGE_X; + else + { + sins1.mMode = ASMIM_ABSOLUTE_X; + sz++; + } + + mIns[di++] = NativeCodeInstruction(ASMIT_LDX, ASMIM_IMMEDIATE, i - 1); + mIns[di++] = lins; + mIns[di++] = sins0; + mIns[di++] = sins1; + mIns[di++] = NativeCodeInstruction(ASMIT_DEX, ASMIM_IMPLIED); + mIns[di++] = NativeCodeInstruction(ASMIT_BPL, ASMIM_RELATIVE, -sz); + + si += 3 * i; + + return true; + } + } + + } + } + + return false; +} + // Size reduction violating various assumptions such as no branches in basic blocks // must be last step before actual assembly -void NativeCodeBasicBlock::BlockSizeReduction(void) +void NativeCodeBasicBlock::BlockSizeReduction(NativeCodeProcedure* proc) { if (!mVisited) { @@ -14412,6 +14602,10 @@ void NativeCodeBasicBlock::BlockSizeReduction(void) { mIns[j++] = mIns[i++]; i++; + } + else if (BlockSizeCopyReduction(proc, i, j)) + { + } else mIns[j++] = mIns[i++]; @@ -14496,6 +14690,8 @@ void NativeCodeBasicBlock::BlockSizeReduction(void) ximm = false; else if (mIns[i].mType == ASMIT_JSR) yimm = ximm = false; + else if (mIns[i].mMode == ASMIM_RELATIVE && mIns[i].mAddress < 0) + yimm = ximm = false; } #endif @@ -14704,9 +14900,9 @@ void NativeCodeBasicBlock::BlockSizeReduction(void) #endif if (mTrueJump) - mTrueJump->BlockSizeReduction(); + mTrueJump->BlockSizeReduction(proc); if (mFalseJump) - mFalseJump->BlockSizeReduction(); + mFalseJump->BlockSizeReduction(proc); } } @@ -18622,8 +18818,6 @@ void NativeCodeProcedure::Compile(InterCodeProcedure* proc) mExitBlock->mIns.Pop(); - CompressTemporaries(); - int frameSpace = tempSave; tempSave = proc->mTempSize > 16 ? proc->mTempSize - 16 : 0; @@ -19201,9 +19395,11 @@ void NativeCodeProcedure::Optimize(void) cnt++; } while (changed); + CompressTemporaries(); + #if 1 ResetVisited(); - mEntryBlock->BlockSizeReduction(); + mEntryBlock->BlockSizeReduction(this); #endif #endif diff --git a/oscar64/NativeCodeGenerator.h b/oscar64/NativeCodeGenerator.h index b1f602a..e86af02 100644 --- a/oscar64/NativeCodeGenerator.h +++ b/oscar64/NativeCodeGenerator.h @@ -158,7 +158,8 @@ public: bool RemoveNops(void); bool PeepHoleOptimizer(NativeCodeProcedure* proc, int pass); - void BlockSizeReduction(void); + void BlockSizeReduction(NativeCodeProcedure* proc); + bool BlockSizeCopyReduction(NativeCodeProcedure* proc, int & si, int & di); bool OptimizeSimpleLoopInvariant(NativeCodeProcedure* proc); bool OptimizeSimpleLoopInvariant(NativeCodeProcedure* proc, NativeCodeBasicBlock * prevBlock, NativeCodeBasicBlock* exitBlock); diff --git a/samples/games/hscrollshmup.c b/samples/games/hscrollshmup.c index 534660d..9fcaa9b 100644 --- a/samples/games/hscrollshmup.c +++ b/samples/games/hscrollshmup.c @@ -8,84 +8,115 @@ #include #include +// Graphics areas in bank 3 byte * const Screen = (byte *)0xc800; byte * const Font = (byte *)0xe000; byte * const TextFont = (byte *)0xe800; byte * const Color = (byte *)0xd800; byte * const Sprites = (byte *)0xd000; -// Character set +// Character set for scrolling background char charset[2048] = { #embed "../resources/hscrollshmupchars.bin" }; +// Character set for status line char tcharset[2048] = { #embed "../resources/breakoutchars.bin" }; +// Tileset for scrolling background char tileset[] = { #embed "../resources/hscrollshmuptiles.bin" }; +// Tilemap for scrolling background char tilemap[128 * 5] = { #embed "../resources/hscrollshmupmap.bin" }; +// Spriteset for player and enemies char spriteset[4096] = { #embed 4096 0 "../resources/hscrollshmupsprites.bin" }; +// Converted tileset char xtileset[16][64]; + +// Converted tilemat char xtilemap[144 * 5]; + +// Horizontal position for the stars in the background char stars[24]; + +// Enemy/shot collision check table char xcollision[256]; +// Align the table data to avoid page crossing when indexing #pragma align(xtileset, 64); #pragma align(xcollision, 256) +// Raster interrupts RIRQCode bottom, top; +// List of enemies struct Enemy { - int px; - byte py; - sbyte dx; - byte state, pad0, pad1, pad2; + int px; // absolute horizontal position + byte py; // absolute vertical position + sbyte dx; // horizontal speed + byte state; // state of the enemy + byte pad0, pad1, pad2; // padding to align at 8 bytes } enemies[5]; +// Shot data +struct Shot +{ + byte ty, x, ry, n; + sbyte dx; // Direction of shot + Shot * next; // Next shot in list +} shots[18]; // Pre-allocated shots + +Shot * freeShot; + + +// Player data struct Player { - int spx; - sbyte vpx; - sbyte ax; - char aphase; - char spy; - char fdelay; + int spx; // absolute screen position + sbyte vpx; // horizontal velocity + sbyte ax; // acceleration + char aphase; // animation phase + char spy; // vertical position + char fdelay; // auto fire delay } player; +// Game state enum GameState { - GS_START, + GS_START, // Waiting for the game to start GS_READY, // Getting ready - GS_PLAYING, - GS_EXPLODING, + GS_PLAYING, // Actually playing + GS_EXPLODING, // Player ship is exploding - GS_GAME_OVER + GS_GAME_OVER // Game is over }; - +// Game data struct Game { - GameState state; - char count; - char edelay; - char ecount; - char escore; - char lives; + GameState state; // Current game state + char count; // Countdown for states that change after delay + char edelay; // enemy wave delay + char ecount; // number of live enemies + char escore; // score not yet accumulated + char lives; // number of player lives left } game; + +// Sound effect for a player shot SIDFX SIDFXFire[1] = {{ 8000, 1000, SID_CTRL_GATE | SID_CTRL_SAW, @@ -95,6 +126,7 @@ SIDFX SIDFXFire[1] = {{ 4, 30 }}; +// Sound effect for enemy explosion SIDFX SIDFXExplosion[1] = {{ 1000, 1000, SID_CTRL_GATE | SID_CTRL_NOISE, @@ -104,6 +136,7 @@ SIDFX SIDFXExplosion[1] = {{ 8, 40 }}; +// Sound effect for player explosion SIDFX SIDFXBigExplosion[3] = { { 1000, 1000, @@ -142,14 +175,19 @@ void status_init(void) Screen[i] = StatusText[i]; } +// update the status line void status_update(void) { + // Set number of lives Screen[38] = game.lives + '0'; // Increment the score from a given digit on if (game.escore) { char at, val = 1; + + // Check for 100s, 10s and 1s and set digit to increment + // appropriately if (game.escore >= 100) { game.escore -= 100; @@ -216,12 +254,20 @@ void score_reset(void) // unpack tiles into a fast accessible format void tiles_unpack(void) { + // The tileset in the loaded binary is organized as an array + // of arrays of 16 characters. These loops reorganize the data + // into 16 arrays of arrays of char. This gives direct 8bit + // index access to each char of a given tile + for(char t=0; t<64; t++) { for(char i=0; i<16; i++) xtileset[i][t] = tileset[16 * t + i]; } + // The left side of the tilemap is duplicated to avoid + // overflow checks at the right border + for(char y=0; y<5; y++) { for(char x=0; x<144; x++) @@ -230,6 +276,10 @@ void tiles_unpack(void) } } + // The shots are stored with row and column, this array allows for + // easy check of collision with an enemy by subtracting the + // character column of the enemy + for(char i=0; i<160; i+=40) { for(char j=0; j<3; j++) @@ -237,35 +287,46 @@ void tiles_unpack(void) } } +// Draw the tiles of one tile row with no horizontal offset +// into line dp + void tiles_draw0(char * dp, char * tm) { + // Second, third and final target line char * ap = dp + 40; char * bp = dp + 80; char * cp = dp + 120; + // target column + char q = 0; for(char x=0; x<10; x++) { + // Get tile char ti = tm[x]; + // First tile column dp[ q] = xtileset[ 0][ti]; ap[ q] = xtileset[ 4][ti]; bp[ q] = xtileset[ 8][ti]; cp[ q] = xtileset[12][ti]; q++; + // Second tile column dp[ q] = xtileset[ 1][ti]; ap[ q] = xtileset[ 5][ti]; bp[ q] = xtileset[ 9][ti]; cp[ q] = xtileset[13][ti]; q++; + // Third tile column dp[ q] = xtileset[ 2][ti]; ap[ q] = xtileset[ 6][ti]; bp[ q] = xtileset[10][ti]; cp[ q] = xtileset[14][ti]; q++; + // Final tile column dp[ q] = xtileset[ 3][ti]; ap[ q] = xtileset[ 7][ti]; bp[ q] = xtileset[11][ti]; @@ -274,6 +335,9 @@ void tiles_draw0(char * dp, char * tm) } } +// Draw the tiles of one tile row with one char horizontal offset +// into line dp + void tiles_draw3(char * dp, char * tm) { char ti = tm[0]; @@ -285,14 +349,17 @@ void tiles_draw3(char * dp, char * tm) char q = 0; for(char x=1; x<11; x++) { + // Start with the fourth column of the previous tile dp[ q] = xtileset[ 3][ti]; ap[ q] = xtileset[ 7][ti]; bp[ q] = xtileset[11][ti]; cp[ q] = xtileset[15][ti]; q++; + // Advance to next tile ti = tm[x]; + // Now three columns of the new tile dp[ q] = xtileset[ 0][ti]; ap[ q] = xtileset[ 4][ti]; bp[ q] = xtileset[ 8][ti]; @@ -391,64 +458,73 @@ void tiles_draw1(char * dp, char * tm) } } - -struct Shot -{ - byte ty, x, ry, n; - sbyte dx; - Shot * next; -} shots[18]; - -Shot * freeShot; - +// Initialize the list of shots void shot_init(void) { + // First shot is just a list head and doubles + // as the last shot in the list as well, so a + // circular list + shots[0].next = shots; shots[0].ty = 6; + // Build list of free shots freeShot = shots + 1; for(char i=1; i<17; i++) shots[i].next = shots + i + 1; shots[17].next = nullptr; } +// Draw one shot inline void shot_draw(char * dp, char i, char xp, char yp) { + // Character below the shot char c = dp[xp]; + // We know there are only 20 shots __assume(i < 20); + // Character code for background with overlayed shot dp[xp] = i | 0xe0; + // Source and target character code char * fsp = Font + 8 * c; char * fdp = (Font + 0xe0 * 8) + 8 * i; + // Copy the background character into the shot character fdp[0] = fsp[0]; fdp[1] = fsp[1]; fdp[2] = fsp[2]; fdp[3] = fsp[3]; fdp[4] = fsp[4]; fdp[5] = fsp[5]; fdp[6] = fsp[6]; fdp[7] = fsp[7]; + // Put the line fdp[yp] = 0x00; } +// Add a shot to the active list void shot_add(int sx, char sy, sbyte dx) { + // Actual screen position char py = sy - 14; char gy = py >> 5; char ey = (py >> 3) & 3; char ry = py & 7; + // Get a new shot structure from the free list Shot * s = freeShot; freeShot = s->next; + // Insert it into the sorted list of active shots Shot * p = shots; while (p->next->ty < gy) p = p->next; s->next = p->next; p->next = s; + // Init structure s->ty = gy; s->ry = ry; s->dx = dx; + // Horizontal start postion based on player sprite if (dx < 0) { char x = (sx) >> 3; @@ -463,33 +539,47 @@ void shot_add(int sx, char sy, sbyte dx) } } +// Draw the screen with tiles, stars and shots at the given +// absolute pixel position void tiles_draw(unsigned x) { // vic.color_border++; + + // Wait for the raster to be below the first tile row vic_waitTop(); while (vic.raster < 106) ; // vic.color_border--; + // Pixel scroll offset char xs = 7 - (x & 7); + // Update raster IRQ for next screen rirq_data(&top, 1, VIC_CTRL2_MCM | xs); rirq_data(&top, 2, ~(1 << xs)); + // Character position x >>= 3; + // Tile position and tile offset char xl = x >> 2, xr = x & 3; char yl = 0; char ci = 0; + // First shot in list Shot * ps = shots; + // Loop over five rows of 4x4 tiles for(int iy=0; iy<5; iy++) { + // Target position char * dp = Screen + 120 + 160 * iy; char * cp = Color + 120 + 160 * iy; + + // Tile source position char * tp = xtilemap + xl + 144 * iy; + // Draw tiles with given char offset switch (xr) { case 0: @@ -508,6 +598,9 @@ void tiles_draw(unsigned x) __assume(false); } + // Draw the four stars for the tile row, empty + // space has char code zero + char k = stars[yl + 0] + 0; if (dp[k]) cp[k] = 8; @@ -544,17 +637,26 @@ void tiles_draw(unsigned x) dp[k] = 0xf8; } + // Loop over all shots in the current tile row Shot *ss = ps->next; while (ss->ty == iy) { + // Advance position ss->x += ss->dx; + + // Decrement live ss->n--; + + // Draw the shot shot_draw(dp, ci++, ss->x, ss->ry); + if (ss->n) ps = ss; else { + // End of live, remove from the list + ps->next = ss->next; ss->next = freeShot; freeShot = ss; @@ -562,41 +664,54 @@ void tiles_draw(unsigned x) ss = ps->next; } + // Next tile row yl += 4; } } +// Intitialize player void player_init(void) { + // Set up start position and direction player.vpx = 0; player.ax = 1; player.spy = 100; player.fdelay = 0; + // Show sprites spr_set(0, true, 160, 100, 64, VCOL_BLUE, true, false, false); spr_set(7, true, 160, 100, 64 + 16, VCOL_MED_GREY, true, false, false); + + // Background has priority before shadow sprite vic.spr_priority = 0x80; } +// Use the joystick to control the player void player_control(void) { + // Poll the joystick joy_poll(0); + // Change horizontal direction if joystick left or right if (joyx[0] != 0) player.ax = joyx[0]; + // Change vertical position if joystick up or down player.spy += 2 * joyy[0]; if (player.spy < 14) player.spy = 14; else if (player.spy > 14 + 159) player.spy = 14 + 159; + // Animate/accelerate based on requested direction if (player.ax > 0) { + // Speed up until max speed if (player.vpx < 32) player.vpx++; + // Adapt animation phase for direction change if (player.aphase > 0 && player.aphase < 16) player.aphase--; else if (player.aphase >= 16 && player.aphase < 32) @@ -614,40 +729,50 @@ void player_control(void) } + // Modulus animation phase player.aphase &= 31; + // Set player sprite image based on animation phase spr_image(0, 64 + (player.aphase >> 1)); spr_image(7, 80 + (player.aphase >> 1)); + // Player position based on speed int px = 148 - 4 * player.vpx; + // Check for player shooting if (player.fdelay) player.fdelay--; else if (joyb[0]) { if (player.aphase < 4 || player.aphase >= 29) { + // Fire right shot_add(px, player.spy, 1); sidfx_play(0, SIDFXFire, 1); } else if (player.aphase >= 13 && player.aphase < 20) { + // Fire left shot_add(px, player.spy, -1); sidfx_play(0, SIDFXFire, 1); } + // Delay next shot by six frames player.fdelay = 6; } + // Move player sprite spr_move(0, 24 + px, 50 + player.spy); spr_move(7, 32 + px, 58 + player.spy); } +// Advance absolute player/screen position void player_move() { player.spx += player.vpx >> 2; } +// Initialize game structure void game_init(void) { game.edelay = 80; @@ -657,23 +782,31 @@ void game_init(void) player.spx = 40; } +// Move all active enemies void enemies_move(void) { + // Loop over potential five enemies for(char i=0; i<5; i++) { + // Check if enemy is active if (enemies[i].state) { + // Advance position enemies[i].px += enemies[i].dx; + // Check for relative position to player int rx = enemies[i].px - player.spx; + if (rx < -192 || rx >= 480) { + // Enemy is outside scope, remove it from active set enemies[i].state = 0; game.ecount--; spr_show(2 + i, false); } else { + // Move enemy sprite spr_move(2 + i, rx + 24, enemies[i].py + 50); if (enemies[i].state & 0x80) @@ -682,11 +815,13 @@ void enemies_move(void) } else { + // Enemy is eploding spr_color(2 + i, VCOL_YELLOW); spr_image(2 + i, 127 - (enemies[i].state >> 2)); enemies[i].state--; if (enemies[i].state == 0) { + // Explosion complete, remove from active set spr_show(2 + i, false); game.ecount--; } @@ -696,49 +831,75 @@ void enemies_move(void) } } +// Spwan one new enemy outside screen void enemies_spawn(void) { + // Seed for enemy char u = rand(); + // Index of enemy char e = game.ecount; + // We know there are only five, help the compiler + // with the indexing __assume(e < 5); + // Speed of enemy sbyte v = 1 + (u & 3); + + // Vertical position enemies[e].py = 29 + 32 * e; + + // Speed and direction enemies[e].dx = player.vpx < 0 ? v : -v; + + // Position left or right outside enemies[e].px = (player.vpx < 0 ? player.spx - 56 : player.spx + 320) + ((u >> 1) & 31); + + // Initial status is alive enemies[e].state = 0x80; + // Set enemy sprite outside screen int rx = enemies[e].px - player.spx; - spr_set(2 + e, true, rx + 24, enemies[e].py + 50, player.vpx < 0 ? 97 : 96, VCOL_LT_BLUE, true, false, false); + + // One more enemy ready game.ecount++; } +// Remove and reset all enemies void enemies_reset(void) { for(char i=0; i<5; i++) { + // Hide sprite and clearout state spr_show(2 + i, false); enemies[i].state = 0x00; } + // Delay for next enemy to show up game.edelay = 80; game.ecount = 0; } +// Check collision player with enemy bool player_check(void) { + // Index of enemy in players tile row char e = (player.spy - 14) >> 5; + + // Is it active? if (e < 5 && (enemies[e].state & 0x80)) { + // Relative horizontal position int rx = enemies[e].px - player.spx; rx -= 148 - 4 * player.vpx; + // Too close, we are toast if (rx >= - 12 && rx <= 12) { + // Enemy explodes as well enemies[e].state = 64; return true; } @@ -747,18 +908,28 @@ bool player_check(void) return false; } +// Check collision shot with enemies bool shots_check(void) { + // First shot in list (actually the head, or the shot + // before the current one) Shot * ps = shots; bool hit = false; + // Loop over all five enemies for(char i=0; i<5; i++) { + // Only check live and active enemies if (enemies[i].state & 0x80) { + // Enemy position on screen sbyte rx = (enemies[i].px - player.spx) >> 3; + + // Check for actually on screen if (rx >= 0 && rx < 40) { + // Ignore all shots in tile rows before the + // current row Shot * ss = ps->next; while (ss->ty < i) { @@ -766,16 +937,25 @@ bool shots_check(void) ss = ps->next; } + // Now loop over all shots in the current tile row while (ss->ty == i) { + // Check horizontal collision if (xcollision[(char)(ss->x - rx)]) { + // Remove shot from active list and put it into free list ps->next = ss->next; ss->next = freeShot; freeShot = ss; + + // Mark enemy as exploding enemies[i].state = 64; + + // Increment score game.escore++; hit = true; + + // Done checking this row break; } @@ -789,6 +969,7 @@ bool shots_check(void) return hit; } +// Advance game state void game_state(GameState state) { // Set new state @@ -823,8 +1004,10 @@ void game_state(GameState state) } } +// Work for current frame void game_loop() { + // Draw the background tiles_draw(player.spx & 4095); switch (game.state) @@ -832,15 +1015,20 @@ void game_loop() case GS_START: break; case GS_READY: + // Scroll to start position player.spx -= game.count >> 2; if (!--game.count) game_state(GS_PLAYING); break; case GS_PLAYING: + // Control player player_control(); + + // Move player player_move(); + // Check for new enemies if (game.edelay) { game.edelay--; @@ -849,30 +1037,38 @@ void game_loop() } else { + // Move enemies enemies_move(); + + // All enemies gone, update delay for next squad if (!game.ecount) game.edelay = 64 + (rand() & 63); } + // Check shots if (shots_check()) sidfx_play(1, SIDFXExplosion, 1); + // Check player enemy collision if (player_check()) game_state(GS_EXPLODING); break; case GS_EXPLODING: + // Animate player spr_image(0, 127 - (game.count >> 2)); spr_image(7, 127 - (game.count >> 2)); player_move(); enemies_move(); + // Wait for explostion to finish if (!--game.count) { enemies_reset(); game.lives--; + // More lives? if (game.lives) game_state(GS_READY); else @@ -886,9 +1082,11 @@ void game_loop() } // vic.color_border--; + // Update sound effects sidfx_loop(); // vic.color_border++; + // Update status line status_update(); } @@ -956,10 +1154,13 @@ int main(void) memset(Color, 0, 80); memset(Color + 80, 8, 920); + // Init sound effects state machine sidfx_init(); + // Full volume sid.fmodevol = 15; + // Init status line status_init(); // initialize background parallax stars