ECE390 Computer Engineering II taken in Spring 2004 at University of Illinois. The following projects involved extensive assembly programming in X86 processor architecture platform
Final Project: Castles & Cannons: 10 seconds
Variation of the classic game Rampart. Cannonball shooting game in 2-dimensional space with rebuild phase in which tetris-like block aligning for defense is involved.
Introduction
C & C: 10 seconds is a variation of a classic game "Rampart." The object of the game is to conquer all the castles in the given region before the other player does.
The game starts off with each player controlling one castle in his/her region. The game is separated into three phases. In the battle phase, the players attack each other with the cannons deployed in their castles. Each battle phase is followed by rebuild phase in which the players are given a limited time to rebuild and repair damage. At the end of the rebuild phase, the players are given additional cannons based on the number of castles conquered, which can be deployed during the following deploy phase. The cycle continues until one of the players conquer all the castles in his/her region or the other player loses all of his/her castles twice in a row.
Problem Description
- Dividing and integrating functions
- Determining different territories bounded by wall pieces laid out by the players
- Data structure for wall pieces and how to deal with rotation and placement
- Keeping track of real-time status of each object and each grid of the game map
- Drawing pre-rendered images on correct wall pieces and implementing visual enhancements such as zooming and animation
- Implementing music and sound effects
- Keyboard inputs
- Possible implementation of mouse inputs
- Possible implementation of network play if time permits
Team members
- Ki Chung: Overall design and flow of the game (main loop, data structures, etc.)
- Graphics implementation
- Game play implementation
- Prepared all the images and graphics used in game (all original images with an exception of explosion and instruction images)
- Hyun Jeong: Sound implementation (all the sound functions)
- Input handling
- Intro design (instruction images)
- Contributed to the design of the game
- Seunghoon Kim: Graphics
- Graphics implementation
- Game play implementation
- Testing and debugging
- Contributed to the design of the game
- Gihyun Ko: Game mechanics and algorithm
- Graphics implementation
- Gamplay implementation
- Testing and debugging
- Contributed to the design of the game
Screen Shots
 
Main Pseudocode
Author: Ki Chung Description: Many of the game specifics needed to be determined before main loop could be written. Naturally, most of the function and data structures were designed while writing the main loop
_Game
if [_Flags] == exit
exit _Game
if phase == break
{
if phase == deploy break
{
_ScrollBanner(_BannerX, _BannerY)
load [_ScreenOff] from backup
_DrawImage([_ScreenOff], SCREEN_WIDTH, SCREEN_HEIGHT, [_DeployBannerOff], DEPLOY_BANNER_WIDTH, DEPLOY_BANNER_HEIGHT, [_BannerX], [_BannerY], 1, 1)
if break time limit reached
{
[_Phase] = deploy
reset [_BannerX] and [_BannerY]
}
}
else if phase == battle break
{
_ScrollBanner(_BannerX, _BannerY)
load [_ScreenOff] from backup
_DrawImage([_ScreenOff], SCREEN_WIDTH, SCREEN_HEIGHT, [_BattleBannerOff], BATTLE_BANNER_WIDTH, BATTLE_BANNER_HEIGHT, [_BannerX], [_BannerY], 1, 1)
if break time limit reached
{
[_Phase] = battle
reset [_BannerX] and [_BannerY]
}
}
else if phase == rebuild break
{
_ScrollBanner(_BannerX, _BannerY)
load [_ScreenOff] from backup
_DrawImage([_ScreenOff], SCREEN_WIDTH, SCREEN_HEIGHT, [_RebuildBannerOff], REBUILD_BANNER_WIDTH, REBUILD_BANNER_HEIGHT, [_BannerX], [_BannerY], 1, 1)
if break time limit reached
{
[_Phase] = rebuild
reset [_BannerX] and [_BannerY]
}
}
}
else
{
_DrawStatusBar([_ScreenOff], STATUSBAR_X, STATUSBAR_Y)
_DrawMap([_MapScreenOff], [_GameMapOff])
if phase == initialize
{
if P1_init == true
{
_UpdateInitCursor([_P1CastleArray], _P1X, _P1Y, [_P1_InputFlags])
_DrawImage([_MapScreenOff], MAP_PIXEL_WIDTH, MAP_PIXEL_HEIGHT, [_P1InitCursorOff], INIT_CURSOR_WIDTH, INIT_CURSOR_HEIGHT, [_P1X] * 8 + 4, [_P1Y] * 8 + 4, NUM_FRAMES_INIT_CURSOR, 1)
_BuildCastle([_GameMapOff], [_P1X], [_P1Y], _NumP1Territory, [_P1_InputFlags], P1_INIT_PHASE)
}
if P2_init == true
{
_UpdateInitCursor([_P2CastleArray], _P2X, _P2Y, [_P2_InputFlags])
_DrawImage([_MapScreenOff], MAP_PIXEL_WIDTH, MAP_PIXEL_HEIGHT, [_P2InitCursorOff], INIT_CURSOR_WIDTH, INIT_CURSOR_HEIGHT, [_P2X] * 8 + 4, [_P2Y] * 8 + 4, NUM_FRAMES_INIT_CURSOR, 1)
_BuildCastle([_GameMapOff], [_P2X], [_P2Y], _NumP2Territory, [_P2_InputFlags], P2_INIT_PHASE)
}
if all players are initialized == true
{
[_Phase] = break before deploy
_DimBuffer([_ScreenOff], SCREEN_WIDTH, SCREEN_HEIGHT, DIM_VAL)
make a copy of the screen
}
}
else if phase == deploy
{
if P1_deploy == true
{
_UpdateCursor(_P1X, _P1Y, 0, 0, GAME_MAP_WIDTH - 1, GAME_MAP_HEIGHT - 1, [_P1_InputFlags])
_DrawDeployCursor([_MapScreenOff], [_P1X] * 8, [_P1Y] * 8, [_P1DeployCursorOff])
_BuildCannon([_GameMapOff], [_P1CannonArray], _NumP1DeployCannon, _NumP1Cannon, [_P1X], [_P1Y], [_P1_InputFlags], P1_REGION, P1_DEPLOY_PHASE)
}
if P2_deploy == true
{
_UpdateCursor(_P2X, _P2Y, 0, 0, GAME_MAP_WIDTH - 1, GAME_MAP_HEIGHT - 1, [_P2_InputFlags])
_DrawDeployCursor([_MapScreenOff], [_P2X] * 8, [_P2Y] * 8, [_P2DeployCursorOff])
_BuildCannon([_GameMapOff], [_P2CannonArray], _NumP2DeployCannon, _NumP2Cannon, [_P2X], [_P2Y], [_P2_InputFlags], P2_REGION, P2_DEPLOY_PHASE)
}
if deploy time limit reached || all players are done deploying == true
{
reset [_TimeTick]
[_Phase] = break before battle
_DimBuffer([_ScreenOff], SCREEN_WIDTH, SCREEN_HEIGHT, DIM_VAL)
make a copy of the screen
}
}
else if phase == battle
{
_DrawExplosion([_MapScreenOff], [_GameMapOff], [_ExplosionArray], [_ExplosionOff])
_UpdateCannonBall([_GameMapOff], [_CBallArray])
_DrawCannonBall([_MapScreenOff], [_CBallArray], [_CannonBallOff])
_UpdateCursor(_P1X, _P1Y, 0, 0, MAP_PIXEL_WIDTH - 1, MAP_PIXEL_HEIGHT - 1, [_P1_InputFlags])
_DrawImage([_MapScreenOff], MAP_PIXEL_WIDTH, MAP_PIXEL_HEIGHT, [_P1BattleCursorOff], BATTLE_CURSOR_WIDTH, BATTLE_CURSOR_HEIGHT, [_P1X], [_P1Y], NUM_FRAMES_BATTLE_CURSOR, 1)
_FireCannon([_GameMapOff], [_P1CannonArray], [_CBallArray], [_P1X], [_P1Y], [_P1_InputFlags])
_UpdateCursor(_P2X, _P2Y, 0, 0, MAP_PIXEL_WIDTH - 1, MAP_PIXEL_HEIGHT - 1, [_P2_InputFlags])
_DrawImage([_MapScreenOff], MAP_PIXEL_WIDTH, MAP_PIXEL_HEIGHT, [_P2BattleCursorOff], BATTLE_CURSOR_WIDTH, BATTLE_CURSOR_HEIGHT, [_P2X], [_P2Y], NUM_FRAMES_BATTLE_CURSOR, 1)
_FireCannon([_GameMapOff], [_P2CannonArray], [_CBallArray], [_P2X], [_P2Y], [_P2_InputFlags])
if battle time limit reached
{
reset [_TimeTick]
[_Phase] = break before rebuild
_DimBuffer([_ScreenOff], SCREEN_WIDTH, SCREEN_HEIGHT, DIM_VAL)
make a copy of the screen
_UpdateEnclosedRegion([_GameMapOff])
_GenerateRandomBlock(_P1CurrentBlock)
[_P2CurrentBlock] = [_P1CurrentBlock]
}
}
else if phase == rebuild
{
_UpdateCursor(_P1X, _P1Y, 0, 0, GAME_MAP_WIDTH - 1, GAME_MAP_HEIGHT - 1, [_P1_InputFlags])
_DrawBlock([_MapScreenOff], [_P1X] * 8, [_P1Y] * 8, [_P1CurrentBlock], [_P1BlockOff])
_RotateBlock(_P1CurrentBlock, [_P1_InputFlags])
_BuildWall([_GameMapOff], [_P1X], [_P1Y], _P1CurrentBlock, [_P1_InputFlags])
_UpdateCursor(_P2X, _P2Y, 0, 0, GAME_MAP_WIDTH - 1, GAME_MAP_HEIGHT - 1, [_P2_InputFlags])
_DrawBlock([_MapScreenOff], [_P2X] * 8, [_P2Y] * 8, [_P2CurrentBlock], [_P2BlockOff])
_RotateBlock(_P2CurrentBlock, [_P2_InputFlags])
_BuildWall([_GameMapOff], [_P2X], [_P2Y], _P2CurrentBlock, [_P2_InputFlags])
if rebuild time limit reached
{
reset [_TimeTick]
if P1 and P2 has at least one castle
{
[_Phase] = break before deploy
update [_NumP1DeployCannon]
update [_NumP2DeployCannon]
dim screen
make a copy of the screen
}
if P1 has no castle
{
decrement [_P1Life]
increment [_P2Life]
[_Phase] = P1 initialize
update [_NumP1DeployCannon]
}
if P2 has no castle
{
decrement [_P2Life]
increment [_P1Life]
[_Phase] = P2 initialize
update [_NumP2DeployCannon]
}
adjust [_P1Life] and [_P2Life] to max
if P1 has no life && P2 has life
{
_DimBuffer([_ScreenOff], SCREEN_WIDTH, SCREEN_HEIGHT, DIM_VAL)
_DrawImage([_ScreenOff], SCREEN_WIDTH, SCREEN_HEIGHT, [_P2WinBannerOff], WIN_BANNER_WIDTH, WIN_BANNER_HEIGHT, WIN_BANNER_X, WIN_BANNER_Y, 1, 0)
loop until exit key pressed
exit _Game
}
else if P2 has no life && P1 has life
{
_DimBuffer([_ScreenOff], SCREEN_WIDTH, SCREEN_HEIGHT, DIM_VAL)
_DrawImage([_ScreenOff], SCREEN_WIDTH, SCREEN_HEIGHT, [_P1WinBannerOff], WIN_BANNER_WIDTH, WIN_BANNER_HEIGHT, WIN_BANNER_X, WIN_BANNER_Y, 1, 0)
loop until exit key pressed
exit _Game
}
}
} ;end of else if phase == rebuild
_CopyBuffer([_MapScreenBuffer], MAP_PIXEL_WIDTH, MAP_PIXEL_HEIGHT, [_ScreenBuffer], SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0)
} ;end of else
display screen buffer on screen
update [_AnimateTick] and [_AnimateCount]
update [_TimeTick] and [_Time]
update [_CBallTick]
loop to _Game
The full source code link available at the bottom of this page.
Functions Explained
_Game function was debugged by everyone as more functions were added.
Although each person listed under authors section wrote all of the code, many of them were later tested and debugged by other group members. As it was hard to keep track of who fixed what where, only the major authors of the functions are listed. Everyone in the group contributed to testing and debugging.
Some of the functions which did not work correctly were rewritten by other group members.
Final code includes all the rewritten and debugged versions.
void _Menu( )
- Author: Ki Chung
- Inputs: -
- Outputs: -
- Returns: -
- Calls: _Game, _Instruction, _Credits, _CopyBuffer, _ComposeBuffers, _CopyToScreen
- The Menu from which the player can choose 'Play Game', 'Instruction', 'Credits', or 'Exit.' Initializes various necessary variables for _Game function when 'Play Game' is selected.
void _Instruction( )
- Author: Hyun Jeong, Gihyun Ko
- Inputs: -
- Outputs: -
- Returns: -
- Calls: _CopyBuffer, _CopyToScreen
- Displays instructions on how to play.
void _Game( )
- Author: Ki Chung
- Inputs: -
- Outputs: -
- Returns: -
- Calls: _UpdateInitCursor, _UpdateCursor, _RotateBlock, _UpdateEnclosedRegion, _BuildCastle, _BuildCannon, _BuildWall, _GenerateRandomBlock, _UpdateCannonBall, _FireCannon, _ScrollBanner, _DrawStatusBar, _DrawMap, _DrawImage, _DrawDeployCursor, _DrawBlock, _DrawCannonBall, _DimBuffer, _ClearBuffer, _CopyBuffer, _ComposeBuffer, _CopyToScreen
- This is the main loop of the game. _Game is called by _Menu when the player chooses the option 'Play Game' _Game then takes care of the entire flow of the game. For the details on how it functions, look at main pseudocode
void _UpdateInitCursor(dword *CastleArray, dword *X, dword *Y, word InputFlags)
- Author: Ki Chung
- Inputs:
- CastleArray - array of castles
- X - offset of x coordinate
- Y - offset of y coordinate
- InputFlags - input flags
- Outputs:
- [X] - X updated with new x coordinate
- [Y] - Y updated with new y coordinate
- Returns: -
- Calls: -
- Updates initialize phase cursor; Since initialize phase cursor needs to move between castles, it looks up the castle array for location info and updates the cursor. (eg. If left key is pressed, the function finds the castle with the closest x distance to the left of current and sets the location of this castle as the current location)
void _UpdateCursor(dword *X, dword *Y, word MinX, word MinY, word MaxX, word MaxY, word InputFlags)
- Authors: initially written by Gihyun Ko, rewritten by Ki Chung
- Inputs:
- X - offset of x coordinate
- Y - offset of y coordinate
- MinX - minimum x of the boundary
- MinY - minimum y of the boundary
- MaxX - maximum x of the boundary
- MaxY - maximum y of the boundary
- InputFlags - input flags
- Outputs:
- [X] - X updated with new x coordinate
- [Y] - Y updated with new y coordinate
- Returns: -
- Calls: -
- Updates cursor depending on which key is pressed; if cursor is out of boundary, wraps around the location.
void _RotateBlock(dword *Block, word InputFlags)
- Authors: Ki Chung, Gihyun Ko
- Inputs:
- Block - offset of block to rotate
- InputFlags - input flags
- Outputs: Block rotated 90 degrees counter-clockwise
- Returns: -
- Calls: -
- Rotates 3 x 3 Block 90 degrees counter-clockwise if secondary key is pressed; Shifts the block word as described the map description section
dword _BoundaryInRegion(dword *MapOff, word X, word Y, word Width, word Height, word RegionVal)
- Authors: Gihyun Ko, Seunghoon Kim, Ki Chung
- Inputs:
- MapOff - offset of map buffer
- X - current x coordinate
- Y - current y coordinate
- Width - width of the region
- Height - height of the region
- RegionVal - map value of particular region
- Outputs: -
- Returns: 1 if boundary is filled with RegionVal; 0 otherwise
- Calls: -
- Compares the values held by the grids specified and the region value passed by the user. For instance, if the cannon image with x and y coordinates is passed in, then the function will compare all four grids with respect to the region value. If any of them does not satisfy the region value, the function will return 0, which means invalid for placement.
dword _BlockInRegion(dword *MapOff, word Block, word X, word Y, word RegionVal)
- Author: Gihyun Ko, Seunghoon Kim
- Inputs:
- MapOff - offset of map buffer
- Block - block to check if it is in region
- X - current x coordinate
- Y - current y coordinate
- RegionVal - map value of particular region
- Outputs: -
- Returns: 1 if Block is filled with RegionVal; 0 otherwise
- Calls: -
- Similar to BoundaryInRegion. The difference is that there are various blocks that have different shapes, therefore occupying only part of the 3x3 grid. The function consists of 9 different comparisons for each grid, and returns 0 or 1, respect to the satisfaction to the region value.
void _UpdateEnclosedRegion(dword *MapOff)
- Author: Seunghoon Kim
- Inputs:
- MapOff - offset of map buffer
- Outputs:
- [_NumP1Territory] - updates number of conquered grid by P1
- [_NumP2Territory] - updates number of conquered grid by P2
- Returns: -
- Calls: -
- Uses a floodfill algorithm presented in MP4. Due to the limited number of recursive calls that can be made in assembly programming, queue implementation was used instead. The function starts off at the center of the map. It enqueues the x and y coordinates for the center of the map, and executes a loop, where the first element in the queue is dequeued, and goes through number of comparisons. If the grid specified is not a wall nor marked, and inside the map region, then its flag will be marked, and its 8 neighbors will be inserted in the queue. It repeats until queue is empty.
void _BuildCastle(dword *MapOff, word X, word Y, word WallVal, dword *NumTerritory, word InputFlags, word CurrentPhase)
- Author: Ki Chung
- Inputs:
- MapOff - offset of map buffer
- X - x coordinate of location around which to build castle walls
- Y - y coordinate of location around which to build castle walls
- WallVal - map value for wall
- NumTerritory - offset of variable holding number of territory
- InputFlags - input flags
- CurrentPhase - current phase
- Outputs:
- MapOff - updates map with correct wall and region values
- [_Phase] - removes CurrentPhase from [_Phase] if walls are built
- NumTerritory - updates amount of territory with NUM_INIT_TERRITORY
- Walls built around (X, Y) in the map buffer pointed to by MapOff
- Returns: -
- Calls: -
- Updates map buffer pointed to by MapOff with walls around location (X, Y) if primary key is pressed. Then it removes CurrentPhase from [_Phase] indicating that the player is ready for the next phase. Also updates NumTerritory to reflect the change in amount of territory conquered
void _BuildCannon(dword *MapOff, dword *CannonArray, dword *NumCannon, dword *TotalCannon, word X, word Y, word InputFlags, word Region, word CurrentPhase)
- Author: initially written by Gihyun Ko, rewritten by Ki Chung
- Inputs:
- MapOff - offset of map buffer
- CannonArray - offset of cannon array
- NumCannon - number of cannons available for deployment
- X - x coordinate of location at which to place cannon
- Y - y coordinate of location at which to place cannon
- InputFlags - input flags
- Region - map value of allowed region for cannon placement
- Outputs:
- NumCannon - number of cannons available updated if cannon is built
- [_Phase] - removes CurrentPhase from [_Phase] if number of cannons reaches 0
- CannonArray - cannon array updated with new cannon
- TotalCannon - total number of cannons available
- MapOff - walls built around (X, Y) in the map buffer pointed to by MapOff
- Returns: -
- Calls: _BoundaryInRegion
- First checks to see if the cannon can be built at the location of the cursor. This is done by calling _BoundaryInRegion. If there's nothing in the cannon's way, it updates the map buffer and the cannon array with new cannon
void _BuildWall(dword *MapOff, word X, word Y, dword *Block, word InputFlags)
- Author: Gihyun Ko
- Inputs:
- MapOff - offset of map buffer
- X - x coordinate of location at which to place block
- Y - y coordinate of location at which to place block
- Block - offset of block to place in the map buffer
- InputFlags - input flags
- Outputs:
- MapOff - map buffer updated with new wall piece
- Block - new wall piece generated and stored in block if old block is used
- Returns: -
- Calls: _BlockInRegion, _GenerateRandomBlock, _UpdateEnclosedRegion
- Takes input InputFlags and checks to see if primary key is pressed. If primary key is pressed then it will check if the land is not occupied for each cell of the 3x3 grid selected for the block. This is done by calling _BlockInRegion. If it's not occupied for cells of the blocks needed, new wall pieces are place by changing the region values and functions _GenerateRandomBlock, and _UpdateEnclosedRegion are called at the end.
void _GenerateRandomBlock(dword *Block)
- Author: Gihyun Ko
- Inputs: _BlockArray - array holding different types of blocks
- Outputs: Block - updated with new wall piece
- Return: -
- Calls: _Random
- Generates a random block by calling the _Random function to find a random value and using it to choose a block from the _BlockArray.
void _UpdateCannonBall(dword *MapOff, dword *CannonBallArray)
- Author: intially written by Gihyun Ko, rewritten by Ki Chung
- Inputs:
- MapOff - offset of map buffer
- CannonBallArray - offset of array of cannon balls in flight
- [_CBallTick] - tick counter for cannon ball update
- Outputs:
- MapOff - map buffer updated if cannon ball destroys cannon or wall
- CannonBallArray - updated with new position info
- _P1CannonArray - P1 cannon array updated if P1 cannon ball landed
- _P2CannonArray - P2 cannon array updated if P2 cannon ball landed
- Returns: -
- Calls: -
- This function uses Bresenham's line algorithm to make the cannon ball fly in line from source to destination. The function takes three locations from cannon ball array. These are source, destination, and current location. It then calculates the line error from the source and destination locations. The line error is used to calculate the current location and stores the current location back into the array. _DrawCannonBall reads this current location to draw the cannon ball. When the current location is matched with the destination, the function calculates the index in the appropriate cannon array to find out which cannon has fired that cannon ball. Then it resets the cannon enabling it to fire again. The destination is also entered into the explosion array to be displayed on screen. If the destination happens to be a wall piece or a cannon, it assigns proper damage to the object then removes it from the map when the damage reaches the max life of the object.
void _FireCannon(dword *MapOff, dword *CannonArray, dword *CannonBallArray, word X, word Y, word InputFlags)
- Author: initially written by Gihyun Ko, rewritten by Ki Chung
- Inputs:
- MapOff - offset of map buffer
- CannonArray - offset of array of cannons
- CannonBallArray - offset of array of cannon balls in flight
- X - x coordinate of destination for cannon ball
- Y - y coordinate of destination for cannon ball
- InputFlags - input flags
- Outputs:
- CannonArray - cannon array updated if cannon fires
- CannonBallArray - cannon ball array updated with new cannon ball
- Returns: -
- Calls: -
- This function first checks if the fire key is pressed. If it is, it searches for cannon that is ready (that does not have a cannon ball that's still flying) in the given cannon array. If it finds one, it sets the cannon as busy, then updates the cannon ball array with three locations, which are source, destination, and current
void _ScrollBanner(dword *BannerX, dword *BannerY)
- Author: Ki Chung
- Inputs:
- BannerX - offset of x coordinate of banner
- BannerY - offset of y coordinate of banner
- [_AnimateTick] - tick counter for animation
- Outputs: banner positions updated
- Returns: -
- Calls: -
- Updates banner position; used when drawing banner across the screen (eg. Deploy, Battle, Rebuild)
dword _Random(word MaxNum)
- Author: Gihyun Ko
- Inputs: MaxNum - max value to be generated
- Outputs: -
- Returns: random value from 0 to (MaxNum - 1)
- Calls: -
- Takes input MaxNum and uses [_TimeTick] and random algorithm to generate random value from 0 to (MaxNum - 1).
void _DrawStatusBar(dword *DestOff, word X, word Y)
- Author: Ki Chung
- Inputs:
- DestOff - offset of destination buffer
- X - x coordinate of the status bar (use this as reference)
- Y - y coordinate of the status bar (use this as reference)
- _StatusBarOff - offset of status bar image
- _BigNumFontOff - offset of big number font image
- _SmallNumFontOff - offset of small number font image
- _BlueNumFontOff - offset of blue number font image
- [_NumP1Castle] - number of conquered P1 castle
- [_NumP2Castle] - number of conquered P2 castle
- [_TimeTick] - tick counter for displaying time left
- [_AnimateTick] - tick counter for animation
- Outputs: DestOff - status bar drawn to the destination buffer pointed to by DestOff
- Returns: -
- Calls: _CopyBuffer, _DrawImage
- Draws status bar which shows game info (remaining time time, number of castles conquered by each player, amount of territory conquered by each player, and number of cannons deployed by each player) to the destination buffer pointed to by DestOff
void _DrawMap(dword *DestOff, dword *MapOff)
- Author: Ki Chung, Seunghoon Kim
- Inputs:
- DestOff - offset of destination buffer
- MapOff - offset of map buffer
- [_TerrainOff] - offset of terrain image
- [_FlatCastleOff] - offset of flat castle image
- [_BattleCastleOff] - offset of battle phase castle image
- [_FlatCannonOff] - offset of flat cannon image
- [_BattleCannonOff] - offset of battle phase cannon image
- [_FlatWallOff] - offset of flat wall image
- [_BattleWallOff] - offset of battle phase wall image
- [_DestroyedCannonOff] - offset of destroyed cannon image
- [_RubblesOff] - offset of rubbles image
- [_OverlayOff] - offset of overlay buffer
- [_Phase] - current phase
- [_AnimateTick] - tick counter for animation
- Outputs: DestOff - map data drawn to the destination buffer pointed to by DestOff
- Returns: -
- Calls: _ClearBuffer, _CopyBuffer, _ComposeBuffer
- Draws map data to the destination buffer pointed to by DestOff; First colors P1 and P2 regions accordingly, then draws castles, cannons, and walls with the data provided by the game map buffer
void _DrawImage(dword *DestOff, word DestWidth, word DestHeight, dword *ImageOff, word ImageWidth, word ImageHeight, word X, word Y, word NumFrames, word FrameCount, dword AlphaBlend)
- Author: Ki Chung
- Inputs:
- DestOff - offset of destination buffer
- DestWidth - width of destination buffer
- DestHeight - height of destination buffer
- ImageOff - offset of image to draw
- ImageWidth - width of image buffer
- ImageHeight - height of image buffer
- X - x coordinate of cursor in grids
- Y - y coordinate of cursor in grids
- NumFrames - number of frames for image
- FrameCount - counter to be used to determine which frame to be drawn
- AlphaBlend - if 1, alpha-blend image to destination buffer
- [_OverlayOff] - offset of overlay buffer
- Outputs: image drawn on the destination buffer at (X, Y)
- Returns: -
- Calls: _ClearBuffer, _CopyBuffer, _ComposeBuffer, _PointInBox
- Draws image at (X, Y) on the destination buffer pointed to by DestOff and alpha-blends if indicated to do so. This function is a very general function which is used by many other graphics function in the game. It first checks to see if the pixel it's trying to draw is outside the boundary given by DestWidth and DestHeight. This is done by calling _PointInBox. Then it calculates the frame to draw from NumFrames and FrameCount. This calculation is done by dividing FrameCount by NumFrames. The remainder of the division is the frame to draw. So if the calling function wants to draw a particular frame in the image, it just needs to pass that frame number along with the width and height of the frame. If the calling function passes some sort of counting variable for the FrameCount, the end result is an animated object drawn on screen. If AlphaBlend is 1, it draws into overlay buffer instead of DestOff so that it can alpha composed.
void _DrawDeployCursor(dword *DestOff, word X, word Y, dword *CursorImageOff)
- Author: Ki Chung, Seunghoon Kim
- Inputs:
- DestOff - offset of destination buffer
- X - x coordinate of cursor in grids
- Y - y coordinate of cursor in grids
- CursorImageOff - offset of deploy cursor image
- [_InvalidCannonOff] - offset of invalid cannon image
- [_OverlayOff] - offset of overlay buffer
- [_AnimateTick] - tick count that determines which sprite to draw
- Outputs: deploy cursor drawn on the destination buffer at (X, Y)
- Returns: -
- Calls: _BoundaryInRegion, _ClearBuffer, _CopyBuffer, _ComposeBuffer
- Draws deploy cursor at (X, Y) on the destination buffer pointed to by DestOff, if the cannon lies over where it cannot be placed, it's drawn with invalid cannon image
void _DrawBlock(dword *DestOff, word X, word Y, word Block, dword *BlockImageOff)
- Author: Seunghoon Kim
- Inputs:
- DestOff - offset of destination buffer
- X - x coordinate of block in grids
- Y - y coordinate of block in grids
- Block - block to draw
- BlockImageOff - offset of block image
- [_InvalidBlockOff] - offset of invalid block image
- [_OverlayOff] - offset of overlay buffer
- [_AnimateTick] - tick count that determines which sprite to draw
- Outputs: block drawn on the destination buffer at (X, Y)
- Returns: -
- Calls: _BlockInRegion, _ClearBuffer, _CopyBuffer, _ComposeBuffer
- Draws the random blocks created at various positions, respect to the rotations made by the player. The function makes a call to BlockInRegion, to check if the block is in the free region, and in respect to the output returned by BlockInRegion, it will print out the picture on each grid, specified by the block numbers. The block numbers used here are randomly generated from the block array, which holds the bit assignment to each grid in 3x3 image.(The bit will be 1, if a block exists in that grid.) I have written separate function subroutines for each of the nine grids, since each grid holds different coordinate values, and each makes a different function calls.
void _DrawExplosion(dword *DestOff, dword *MapOff, dword *ExplosionArray, dword *ExplosionImage)
- Author: Ki Chung
- Inputs:
- DestOff - offset of destination buffer
- MapOff - offset of map buffer
- ExplosionArray - array of explosions to draw
- ExplosionImage - offset of explosion image
- [_AnimateTick] - tick count for animation (updates ExplosionArray)
- Outputs: explosions drawn to the buffer pointed to by DestOff
- Returns: -
- Calls: _CopyBuffer
- Draws explosions on the destination buffer pointed to by DestOff
void _DrawCannonBall(dword *DestOff, dword *CannonBallArray, dword *CannonBallImage)
- Author: Ki Chung
- Inputs:
- DestOff - offset of destination buffer
- CannonBallArray - array of cannon balls to draw
- CannonBallImage - offset of cannon ball image
- Outputs: cannon balls drawn to the buffer pointed to by DestOff
- Returns: -
- Calls: _CopyBuffer
- Draws cannon balls on the destination buffer pointed to by DestOff
void _DimBuffer(dword *DestOff, word DestWidth, word DestHeight, word DimVal)
- Author: Gihyun Ko
- Inputs:
- DestOff - offset of image buffer in memory
- DestWidth - width of the buffer
- DestHeight - height of the buffer
- DimVal - color to clear with
- Outputs: color copied to buffer
- Returns: -
- Calls: -
- Takes input DimVal and dims the buffer by that constant. Each pixel contains ARGB bytes and each bytes are unpacked into a mmx register and subracted by the DimVal using saturation to prevent the colors going bright again when it goes below 0. Then the mmx register is packed again to dword size and changed for each pixel in the DestOff with width DestWidth and height DestHeight.
dword _InstallTmr( )
- Author: Ki Chung
- Inputs: -
- Outputs: -
- Returns: 1 if error; 0 otherwise
- Calls: _LockArea, _Install_Int
- Installs Timer ISR
void _RemoveTmr( )
- Author: Ki Chung
- Inputs: -
- Outputs: -
- Returns: -
- Calls: -
- Uninstalls Timer ISR
void _TmrISR( )
- Author: Ki Chung
- Inputs: -
- Outputs:
- [_TimeTick] - interrupt counter for controlling phase duration
- [_AnimateTick] - counter for animation
- [_CBallTick] - counter for updating cannon ball array
- Returns: -
- Calls: -
- Increments [_TickCount]
dword _InstallKbd( )
- Author: Hyun Jeong
- Inputs: -
- Outputs: -
- Returns: 1 if error; 0 otherwise
- Calls: _LockArea, Install_Int
- Locks the appropriate memeory using LockArea library function and installs the ISR with Install_Int. This is almost entirely from MP4.
void _RemoveKbd( )
- Author: Hyun Jeong
- Inputs: -
- Outputs: -
- Returns: -
- Calls: _Remove_Int
- Removes the keyboard interrupt handler using the library function, _Remove_Int. Also from MP4.
void _KbdISR( )
- Author: Hyun Jeong
- Inputs: key presses waiting at port [_kbPort], [_kbIRQ]
- Outputs: [_P1InputFlags], [_P2InputFlags], [_Flags]
- Returns: -
- Calls: -
- Handles keyboard input, Very long and tedious function. Takes the scancode from the [kbPort] and compares the scancode with every possible key in this game. There are total of 12 keys, and each press and releases are handled. With presses, sets the appropriate flag to 1. With releases, sets the appropriate flags to 0. The P1 and P2 input flags are updated, along with the general game [_Flag] so it can be used in the game. At the end, it sends acknowledges to the PIC, which was taken from the lab manual.
dword _InstallSound( )
- Author: Hyun Jeong
- Inputs: -
- Outputs: installs sound
- Returns: 1 if error; 0 otherwise
- Calls: _SB16_Init, _DMA_Lock_Mem
- installs the sound. By doing the follwing:
- It sets the [_DMA_Mix] flag, which indicates whether to mix the sound effect to the background music.
- Initializes the [_ISR_Called] to 0, which counts the number of times the soundISR is called.
- The DMA buffer is allocated to the constant "SIZE", first set in the code. The size I ended up using is 4096 bytes. The smallest I was able to use was 2048 bytes. Smaller sizes made heavy pop and crack noises. Perhaps it took too many CPU cycles to correctly refill the buffers on time. There was no difference with 4096 bytes, so it was used to save CPU cycles. After allocating the memory, the selector and the address were stored into [DMASel] and [DMAAddr] respectively.
void _SoundISR( )
- Author: Hyun Jeong
- Inputs: -
- Outputs: sound played
- Returns: -
- Calls: _DMA_Refill
- This is the handler for sounds. This is called regularly after playing specified number of samples indicated in _SB16_Start function. It was set to 2048 samples, which is 2048 bytes for 8-bit sound. It is half the buffer size, so half of the buffer could be refilled each time. The SoundISR simply counts the number of time it's called and calls _DMA_Refill to refill the half of the DMA buffer.
void _DMA_Refill( )
- Author: Hyun Jeong
- Inputs: -
- Outputs: DMA Buffer Refilled, sound mixed
- Returns: -
- Calls: _SB16_Start
- Refilling the buffer - This was the key to playing long sound files with smooth transitions. Each time it is called, it refills half of the buffer with the next half-buffer-length of the sound clip to be played. [_DMA_Refill_Flag] indicates which half to refill. The flag is updated every time one half is filled, so the other half is filled the next time this function is called.
- The actual refill was just copying the next half-buffer length of the sound clip to the DMA buffer address. The next portion to be played was stored in [BGMPos], which is incremented by SIZE/2 each time. [NextPos] is just a variable starting at 0 incremented each time by SIZE/2 also, which is used for comparing to handle the end case. When [NextPos] is greater than [BGMSize], which is the size of the background music, we know we've reached the end, and jump to the end case.
- The end case was handled rather crudely. The code was originally written to play the remaining size of the file, switching to single cycle. But with small enough DMA buffer and having the sound file with enough blank at the end, simply not playing small remainder was good enough. Therefore, when the end is reached, the small remainder is ignored and the music starts again from the beginning.
- Mixing the sounds - Next, it checks the [_DMA_Mix] flag to see whether it needs to mix sound effects to the background music. In this program, background music was always on, so whenever we wanted to play a sound effect, it had to mixed with the background music. To mix the sounds, it loaded data from the buffer it just refilled into an MMX register. It loads the sound effect to be mixed into another MMX register and performs bytewise parallel add. The result is stored back into the buffer and it is repeated until the entire half buffer is filled.
- [MixCycle] stores how many times cycles (DMA refills) the sound must be mixed. It is initialized in _PlaySFX. It is decremented each time, and when it reaches 0, it jumps to the end case. This end case was also handled similar to the DMA refilling case. Again with small enough buffer and sound files with blanks at the end, I simply set the [_DMA_Mix] flag back to 0.
Functions from MP4
dword _PlayBGM(dword FileOff, dword filesize)
- Author: Hyun Jeong
- Inputs: FileOff - offset of the background music to play
- Outputs: sound played
- Returns: 1 if error, 0 otherwise
- Calls: _DMA_Start, _DMA_Stop, _SB16Init, _SB16_GetChannel, _SB16_SetFormat, _SB16_SetMixers, _SB16_Start
- This function starts the music playing. It sets the appropriate flags and position markers to be used in refilling. It initially fills the entire buffer with the beginning of the sound clip. Then it uses the library functions to initialize sound and to start the DMA transfer, etc.
dword _StopBGM(dword crossfade)
- Author: Hyun Jeong
- Inputs: .Crossfade (1 for crossfade and 0 otherwise)
- Outputs: Stops BGM
- Returns: 1 if error, 0 otherwise
- Calls: -
- Stops the background music This function stops the sound. The "crossfade" decreases the volume gradually then bring the music to stop. It was an attempt to reduce pop or crack noise when stopping the sound. This didn't make any noticeable change. Perhaps the CPU cycle was too fast and each instruction was executed too fast to make it noticeable to our ears. Each decrement in volume could have been made with longer intervals, perhaps using the timer or at the ISR. In theory, when we are near the end of the music (maybe about several buffer lengths from the end), or when this function is called, we can decrease the volume each time the ISR is called. However, there was neither time nor significant reason for this to be implemented.
dword _PlaySFX(dword fileOff2, dword filesize2)
- Author: Hyun Jeong
- Inputs: fileOff2 - offset of the sound effect to play, size of the sound clip
- Outputs: sound played
- Returns: 1 if error, 0 otherwise
- Calls: -
- This function does not actually do any "playing." This just sets the variables used for mixing. Sets the [_DMA_Mix] flag to 1, and sets SFX position markers to the next address to refill. [_MixCycles], the number of buffer-refill cycles is found by dividing the size of the sound effect by the size of the half-buffer. The remainder from this division is the length of the end case, stored in [RemCycle]. Since background music is always on, this does not need to start any DMA transfers.
dword _PointInBox(word X, word Y, word BoxULCornerX, word BoxULCornerY, word BoxLRCornerX, word BoxLRCornerY)
- Inputs:
- X - x coordinate of point in question
- Y - y coordinate of point in question
- BoxULCornerX - x coordinate of upper-left hand corner of box
- BoxULCornerY - y coordinate of upper-left hand corner of box
- BoxLRCornerX - x coordinate of lower_right hand corner of box
- BoxLRCornerY - y coordinate of lower_right hand corner of box
- Outputs: -
- Returns: 1 if BoxULCornerX <= X <= BoxLRCornerX and BoxULCornerY <= Y <= BoxLRCornerY; 0 otherwise
- Calls: -
- determines if the point (X, Y) is located in the box formed by the points (BoxULCornerX, BoxULCornerY) and (BoxLRCornerX, BoxLRCornerY)
void _ClearBuffer(dword *DestOff, word DestWidth, word DestHeight, dword Color)
- Inputs:
- DestOff - offset of image buffer in memory
- DestWidth - width of the buffer
- DestHeight - height of the buffer
- Color - color to clear with
- Outputs: color copied to buffer
- Returns: -
- Calls: -
- Clears pointed buffer by filling it with Color
void _CopyBuffer(dword *SrcOff, word SrcWidth, word SrcHeight, dword *DestOff, word DestWidth, word DestHeight, word X, word Y)
- Inputs:
- SrcOff - offset of source buffer
- SrcWidth - width of source buffer
- SrcHeight - height of source buffer
- DestOff - offset of destination buffer
- DestWidth - width of destination buffer
- DestHeight - height of destination buffer
- X - x coordinate of start point in destination buffer
- Y - y coordinate of start point in destination buffer
- Outputs: source buffer copied to destination buffer
- Returns: -
- Calls: -
- Copies the pointed source buffer to (X,Y) in the pointed destination buffer
void _ComposeBuffers(dword *SrcOff, word SrcWidth, word SrcHeight, dword *DestOff, word DestWidth, word DestHeight, word X, word Y)
- Inputs:
- SrcOff - offset of source buffer
- SrcWidth - width of source buffer
- SrcHeight - height of source buffer
- DestOff - offset of destination buffer
- DestWidth - width of destination buffer
- DestHeight - height of destination buffer
- X - x coordinate of start point in destination buffer
- Y - y coordinate of start point in destination buffer
- Outputs: source buffer alpha composed onto destination buffer
- Returns: -
- Calls: -
- Alpha composes source buffer onto destination buffer
External Routines(Pmode Lib): _LibInit _LibExit
_AllocMem
_LoadPNG
_OpenFile
_ReadFile
_CloseFile
_FindGraphicsMode
_SetGraphicsMode
_CopyToScreen
_LockArea
_Install_Int
_Remove_Int
_DMA_Allocate_Mem
_DMA_Start
_DMA_Stop
_DMA_Lock_Mem
_SB16Init
_SB16_GetChannel
_SB16_SetFormat
_SB16_SetMixers
_SB16_Start
_SB16_Stop
_SB16_Exit
Source Code: final.asm
|
 Updating...
Assembly-program-executables.zip (353k) Hoonio, Feb 11, 2012, 8:59 AM
Hoonio, Feb 11, 2012, 8:58 AM
Hoonio, Feb 11, 2012, 8:58 AM
Hoonio, Feb 11, 2012, 8:58 AM
Hoonio, Feb 11, 2012, 8:58 AM
Hoonio, Feb 11, 2012, 8:58 AM
Hoonio, Feb 11, 2012, 8:58 AM
|