Tourney Lad
Tournament ladder leaderboard to allow organizers submit, rank, and edit players.
A web application to store names, teams, and stats of players in any sort of competitive ranked ladder system, Tourney Lad was built using the ASP.NET Core web framework which utilized C#, HTML, and CSS. The front end uses ASP.NET Core Razor pages, which was used due to the fact it has many similarities to a Model-View-Controller architecture. The backend uses C# in combination with Razor pages to deliver the responsive UI for organizers to sort all players by. The project first started off with creating the user account system. Luckily, ASP.NET Core has a built-in authentication system, where it can develop models, setup Razor pages, and create migrations to a database to allow for it all to be done easily. This also allows for two factor authentication, email and password recovery, and data deletion. After implementing the pre-set authentication system, I then got to developing and designing a way to connect the framework to Microsoft SQL Server, as this was the best SQL database for this project. To do this, I developed the create action in the CRUD operations first, in which the Razor page would gather input from the frontend of the page into a model, deliver that model to the controller of the page, create an SQL query, and execute the command.
Create.cshtml
<div class="row mb-3">
<label class="col-sm-3 col-form-label">Player Name</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="playername" value="@Model.PlayerInfo.PlayerName" />
</div>
</div>
<div class="row mb-3">
<label class="col-sm-3 col-form-label">Team Name</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="teamname" value="@Model.PlayerInfo.TeamName" />
</div>
</div>
Create.cshtml.cs
String sqlCmd = "INSERT INTO AuthSystemDB.dbo.Players (Id, PlayerName, TeamName, Wins, Losses, Avatar, Creator) VALUES (@id, @playername, @teamname, @wins, @losses, @avatar, @creator);";
using (SqlCommand command = new SqlCommand(sqlCmd, connection))
{
command.Parameters.AddWithValue("@id", Guid.NewGuid().ToString("N"));
command.Parameters.AddWithValue("@playername", PlayerInfo.PlayerName);
command.Parameters.AddWithValue("@teamname", PlayerInfo.TeamName);
command.Parameters.AddWithValue("@wins", PlayerInfo.Wins);
command.Parameters.AddWithValue("@losses", PlayerInfo.Losses);
command.Parameters.AddWithValue("@avatar", PlayerInfo.Avatar);
command.Parameters.AddWithValue("@creator", currentUserID);
command.ExecuteNonQuery();
}
<div class="row mb-3">
<label class="col-sm-3 col-form-label">Player Name</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="playername" value="@Model.PlayerInfo.PlayerName" />
</div>
</div>
<div class="row mb-3">
<label class="col-sm-3 col-form-label">Team Name</label>
<div class="col-sm-6">
<input type="text" class="form-control" name="teamname" value="@Model.PlayerInfo.TeamName" />
</div>
</div>
String sqlCmd = "INSERT INTO AuthSystemDB.dbo.Players (Id, PlayerName, TeamName, Wins, Losses, Avatar, Creator) VALUES (@id, @playername, @teamname, @wins, @losses, @avatar, @creator);";
using (SqlCommand command = new SqlCommand(sqlCmd, connection))
{
command.Parameters.AddWithValue("@id", Guid.NewGuid().ToString("N"));
command.Parameters.AddWithValue("@playername", PlayerInfo.PlayerName);
command.Parameters.AddWithValue("@teamname", PlayerInfo.TeamName);
command.Parameters.AddWithValue("@wins", PlayerInfo.Wins);
command.Parameters.AddWithValue("@losses", PlayerInfo.Losses);
command.Parameters.AddWithValue("@avatar", PlayerInfo.Avatar);
command.Parameters.AddWithValue("@creator", currentUserID);
command.ExecuteNonQuery();
}
After developing the create, I decided to do the read, update, and delete operations all in one by creating a table that displays all the players in the tournament, along with their stats. Although if the user is signed in and authenticated, which would be only for tournament organizers, they can both edit and delete each individual player. These operations can only be accessed by those who have an account, as it would redirect to the login page if the user was not logged in. The edit options show all the current entries for that player and allows the user to change them easily. Overall, the website allows for users to effortlessly input players and have them show up in the ladder. Although the page layout and CSS may be a bit simple, for this prototype of a web application, it works extremely well, functions exactly how it should, and is developed with ease and security in mind.
Decked Out
OpenGL developed card matching game.
A matching card game utilizing the OpenGL graphics library, Decked Out, was a simple project to understand and develop with a more barebones graphics library. Using it meant I had to put more understanding into all the small details of a user interfacing with a game. I knew right of the start I wanted the game to be heavily inspired from the likes of Bejeweled. I designed the game to be in a grid of eight by eight, where the player would be able to move a card one space over at a time, in a vertical or horizontal direction. If the player moved a card piece into a row or column of matching cards, then they would gain points. The spots opened by the matched pieces would then be replaced with the card above it. The player would try to aim to get the highest score possible within the time limit. There are some differences between the original game and this one, where players are not punished for moving cards in a direction that does not have a match, that piece instead stays there. The main challenge is having a timer to stop the player from playing anymore.
//Checks for matches and marks them for removal
void match(void)
{
bool matchedGrid[8][8] = { false };
//Horizontal check
for(int y = 0; y < 8; ++y)
for (int x = 0; x < (8 - 2); ++x)
{
int currentType = grid[x][y].kind;
if (currentType == -1)
continue;
if (grid[x + 1][y].kind == currentType && grid[x + 2][y].kind == currentType)
{
matchedGrid[x][y] = matchedGrid[x + 1][y] = matchedGrid[x + 2][y] = true;
score++;
}
}
//Vertical check
for (int x = 0; x < 8; ++x)
for (int y = 0; y < (8 - 2); ++y)
{
int currentType = grid[x][y].kind;
if (currentType == -1)
continue;
if (grid[x][y+1].kind == currentType && grid[x][y+2].kind == currentType)
{
matchedGrid[x][y] = matchedGrid[x][y + 1] = matchedGrid[x][y + 2] = true;
score++;
}
}
//Mark for removal
for(int y = 0; y < 8; ++y)
for (int x = 0; x < 8; ++x)
{
if (matchedGrid[x][y])
{
grid[x][y].kind = -1;
}
}
return;
}
Developing for this game was interesting as it required a bit more mathematics, structures, and manipulation of two-dimensional arrays all while developing it alongside the OpenGL library. I first designed a struct as store the card type of each element in the 2-D array. I then started to develop the graphics of each of the cards, with 6 different shapes and colors, each requiring the screen space of each grid piece and calculating the shapes off the dimensions of those spaces. I then developed for the other functions, such as the swap function, which swapped cards. After this swap function, several other functions would happen, which includes the match function, which checks for any matches in any of the columns or rows, remove those cards, and update the score counter. After matches have been found, the cards would then be filled in with random cards. The match function would be run again as to check for any cascading pieces, where there is automatically a match from the random pieces. These would be added to the score as well. Finally, the score would be output as well as the drawing of all the shapes again. All these functions would be executed under an idle function, running every second. Overall, this code was well executed and had minimal bugs, with the only exception being the clock for the timer, as it was based on the code run time, which differs from machine to machine, where a delta time function would’ve eliminated such timing errors.
Shell with Filesystem
C++ command line with implementation of a FAT filesystem.
A simple Linux-based C++ shell, this program could read, copy, search, and add new files based on a free list dynamic memory allocation. These commands and their functions are all within several header and source files. The first functions that were developed was for the shell, and it included reading the commands of the user, including any option flags, and sending them to the shell. These commands include ls(), add(), del(), and copy(). After these functions were implemented, it was then to working on the disk, which would be able to handle all the disk block management of the FAT and getting info about each block of data such as block size and the number of blocks. The next program, the block program, dealt with relaying information of each block to the other programs. The last crucial program, filesys, was implemented to allow the program to communicate with the text files that served as the storage location. It created the filesystems, built and initialized them, as well as reading and deleting any information in them. It also dealt with synching the filesystem to the disk. With these programs implemented, the next steps included dealing with the shell commands itself as well as implementing index search functionality based on some given data.
filesys.cpp
//This function allocates blocks on the disk by updating the freelist of blocks.
int Filesys::addblock(string file, string block)
{
int allocate;
for (int i = 0; i < filename.size(); i++)
{
if (filename[i] == file)
{
allocate = fat[0];
fat[0] = fat[fat[0]];
fat[allocate] = 0;
firstblock[i] = 0;
return 1;
}
else
{
return 0;
}
}
// - check if file exists
// - allocate block (allocate = fat[0])
// - fat[0] = fat[fat[0]] - updates the free list
// - fat[allocate] = 0 - allocated block is the last block
// - update blocklist for the file
// - firstblock = 0
// - firstblock != 0
return 0;
}
The final pieces of code for this project included developing the table search functions, called table in the program, where it would search a text file using indexes to get faster results. The index, in this case, was the given data text file for each event. The index program would build a table based on that index, allowing for a key to be searched from the table, resulting in faster search times than manually going through each record. It would also have a stop word so that if the key reached the end of the table, it would stop the search there. Finally, to put everything together and for it all to run, I developed the main program file, which kept a perpetual loop for commands to be inserted as well as having the ability to have a quit command. This project taught me a lot about the basics of shells, how filesystems work, as well as more experience with C++ data structures, file input and output. It also taught me how to work with different data structures, as well as B-trees and linked lists, as the file structure implemented here followed a linked list.
table.cpp
//Searches the table of indexes for the specfic key, and returns -1 if record not found.
int Table::indexSearch(string key)
{
string buffer;
string buffer2;
int b = getfirstblock(indexfile);
//error checking is in order here
while (b != 0)
{
readblock(indexfile, b, buffer);
buffer2 += buffer;
b = nextblock(indexfile, b);
}
istringstream istream;
istream.str(buffer2);
string ikey;
int iblock;
istream >> key >> iblock;
while (ikey != "kangaroo")
{
if(ikey == key)
{
return iblock;
}
istream >> ikey >> iblock;
}
return -1;
}