Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34de3c1abc | ||
|
|
311d62a719 | ||
|
|
4217bf3703 | ||
|
|
fd68ada82e | ||
|
|
7c77cd0e52 | ||
|
|
93bdb3518a | ||
|
|
69e1e8450d | ||
|
|
e74de40d2e | ||
|
|
9b86bfafe0 |
@@ -11,6 +11,19 @@ public class ApiTests : PageTest
|
|||||||
private IAPIRequestContext? _apiContext;
|
private IAPIRequestContext? _apiContext;
|
||||||
private TestConfiguration Config => TestConfiguration.Instance;
|
private TestConfiguration Config => TestConfiguration.Instance;
|
||||||
|
|
||||||
|
[OneTimeSetUp]
|
||||||
|
public async Task OneTimeSetUp()
|
||||||
|
{
|
||||||
|
if (!Config.Playwright.Headless)
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("HEADED", "1");
|
||||||
|
}
|
||||||
|
if (Config.Playwright.SlowMotion > 0)
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("PWSLOWMO", Config.Playwright.SlowMotion.ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public async Task Setup()
|
public async Task Setup()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class ChessController(
|
|||||||
private static readonly ConcurrentDictionary<Guid, CancellationTokenSource> _gameRemovalCancellationTokens = [];
|
private static readonly ConcurrentDictionary<Guid, CancellationTokenSource> _gameRemovalCancellationTokens = [];
|
||||||
|
|
||||||
private static readonly TimeSpan _computerGameTimeout = TimeSpan.FromHours(1);
|
private static readonly TimeSpan _computerGameTimeout = TimeSpan.FromHours(1);
|
||||||
private static readonly TimeSpan _multiplayerGameTimeout = TimeSpan.FromSeconds(5);
|
private static readonly TimeSpan _multiplayerGameTimeout = TimeSpan.FromDays(1);
|
||||||
private static readonly TimeSpan _gameCleanupTimeout = TimeSpan.FromMinutes(1);
|
private static readonly TimeSpan _gameCleanupTimeout = TimeSpan.FromMinutes(1);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -165,7 +165,7 @@ public class ChessController(
|
|||||||
var expectedPlayerId = isWhiteMove ? gameState.WhitePlayerId : gameState.BlackPlayerId;
|
var expectedPlayerId = isWhiteMove ? gameState.WhitePlayerId : gameState.BlackPlayerId;
|
||||||
|
|
||||||
if (moveDto.PlayerId != expectedPlayerId)
|
if (moveDto.PlayerId != expectedPlayerId)
|
||||||
return Forbid("You are not the current player.");
|
return StatusCode(403, "You are not the current player.");
|
||||||
|
|
||||||
// Make sure player owns the piece
|
// Make sure player owns the piece
|
||||||
var piece = gameState.Pieces.FirstOrDefault(p => p.Id == moveDto.PieceId);
|
var piece = gameState.Pieces.FirstOrDefault(p => p.Id == moveDto.PieceId);
|
||||||
@@ -174,7 +174,7 @@ public class ChessController(
|
|||||||
return NotFound("Chess piece Id does not exist");
|
return NotFound("Chess piece Id does not exist");
|
||||||
|
|
||||||
if ((isWhiteMove && piece.Color != PieceColor.White) || (!isWhiteMove && piece?.Color != PieceColor.Black))
|
if ((isWhiteMove && piece.Color != PieceColor.White) || (!isWhiteMove && piece?.Color != PieceColor.Black))
|
||||||
return Forbid("You cannot move this piece.");
|
return StatusCode(403, "You cannot move this piece.");
|
||||||
|
|
||||||
var result = chessService.MakeMove(gameState, moveDto);
|
var result = chessService.MakeMove(gameState, moveDto);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UserSecretsId>53ed685c-bdff-4306-8cc2-9fbe55c85713</UserSecretsId>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -16,14 +16,14 @@
|
|||||||
<div id="ProjectsBox">
|
<div id="ProjectsBox">
|
||||||
<div id="ChessProject" class="projectContainer">
|
<div id="ChessProject" class="projectContainer">
|
||||||
<div class="displayBox diagonal-section-left">
|
<div class="displayBox diagonal-section-left">
|
||||||
<a target="_blank" href="https://github.com/JoshHeaps/OnlineChess">
|
<a target="_blank" href="https://github.com/JoshHeaps/JoshHeaps.Net/blob/master/JoshHeaps.Net/Pages/Chess.cshtml">
|
||||||
<img id="ChessImage" class="projectImage projectImageLeft" src="/images/Chess.jpg" title="Chessboard"/>
|
<img id="ChessImage" class="projectImage projectImageLeft" src="/images/Chess.jpg" title="Chessboard"/>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div id="ChessText" class="projectTextRight">
|
<div id="ChessText" class="projectTextRight">
|
||||||
<h2 id="ChessTitle" class="projectHeaderRight projectHeader">Chess</h2>
|
<h2 id="ChessTitle" class="projectHeaderRight projectHeader">Chess</h2>
|
||||||
<p id="ChessDescription" class="projectDescriptionRight projectDescription">
|
<p id="ChessDescription" class="projectDescriptionRight projectDescription">
|
||||||
As someone who loves chess, I wanted to create a chess game that I could play with my friends and family. This project is a work in progress, as currently it can only be played locally. It was built in C# on .NET 8.0, using winforms, because I like a challenge. I plan to add online multiplayer functionality in the future.
|
As someone who loves chess, I wanted to create a chess game that I could play with my friends and family. It was made using .NET razor pages, a .NET web api backend, and a good amount of javascript on the frontend. Feel free to check it out in the demos below :)
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,31 +8,23 @@ public class AutoIpUpdateService(
|
|||||||
ILogger<AutoIpUpdateService> log)
|
ILogger<AutoIpUpdateService> log)
|
||||||
: BackgroundService
|
: BackgroundService
|
||||||
{
|
{
|
||||||
private static readonly TimeSpan CheckInterval = TimeSpan.FromMinutes(5);
|
|
||||||
private static readonly HttpClient httpClient = new();
|
|
||||||
public static bool IsEnabled { get; private set; } = false;
|
public static bool IsEnabled { get; private set; } = false;
|
||||||
|
|
||||||
|
private static readonly TimeSpan _checkInterval = TimeSpan.FromMinutes(1);
|
||||||
|
private static readonly HttpClient _httpClient = new();
|
||||||
|
|
||||||
protected override async Task ExecuteAsync(CancellationToken stop)
|
protected override async Task ExecuteAsync(CancellationToken stop)
|
||||||
{
|
{
|
||||||
IsEnabled = true;
|
IsEnabled = true;
|
||||||
var timer = new PeriodicTimer(CheckInterval);
|
var timer = new PeriodicTimer(_checkInterval);
|
||||||
AAAARecord dnsRecord = await GetDnsRecordAsync();
|
|
||||||
string lastKnownIp = dnsRecord.Content;
|
|
||||||
|
|
||||||
while (await timer.WaitForNextTickAsync(stop))
|
while (await timer.WaitForNextTickAsync(stop))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string currentIp = await GetPublicIpAsync() ?? "";
|
await UpdateIpAddressIfChanged();
|
||||||
|
|
||||||
if (lastKnownIp != currentIp)
|
|
||||||
{
|
|
||||||
await UpdateDnsIpAsync(config, dnsRecord, currentIp);
|
|
||||||
|
|
||||||
lastKnownIp = currentIp;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) { /* shutting down */ }
|
catch (OperationCanceledException) { break; }
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
log.LogError(ex, "Error while attempting ip update");
|
log.LogError(ex, "Error while attempting ip update");
|
||||||
@@ -40,11 +32,33 @@ public class AutoIpUpdateService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task UpdateIpAddressIfChanged()
|
||||||
|
{
|
||||||
|
var dnsRecords = await GetDnsRecordAsync();
|
||||||
|
string dnsRecordIp = dnsRecords[0].Content;
|
||||||
|
|
||||||
|
if (!dnsRecords.Records.All(x => x.Content == dnsRecords[0].Content))
|
||||||
|
dnsRecordIp = string.Empty;
|
||||||
|
|
||||||
|
string publicIp = await GetPublicIpAsync() ?? string.Empty;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(publicIp) || dnsRecordIp == publicIp)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var dnsRecord in dnsRecords.Records)
|
||||||
|
{
|
||||||
|
if (dnsRecord.Content == publicIp)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
await UpdateDnsIpAsync(config, dnsRecord, publicIp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static async Task<string> GetPublicIpAsync()
|
private static async Task<string> GetPublicIpAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return await httpClient.GetStringAsync(@"https://api.ipify.org/");
|
return await _httpClient.GetStringAsync(@"https://api.ipify.org/");
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -55,7 +69,7 @@ public class AutoIpUpdateService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<AAAARecord> GetDnsRecordAsync()
|
private async Task<RecordList> GetDnsRecordAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -64,8 +78,8 @@ public class AutoIpUpdateService(
|
|||||||
cfClient.DefaultRequestHeaders.Add("X-Auth-Key", config["cfKey"]);
|
cfClient.DefaultRequestHeaders.Add("X-Auth-Key", config["cfKey"]);
|
||||||
var result = await cfClient.GetAsync(@$"https://api.cloudflare.com/client/v4/zones/{config["zoneId"]}/dns_records");
|
var result = await cfClient.GetAsync(@$"https://api.cloudflare.com/client/v4/zones/{config["zoneId"]}/dns_records");
|
||||||
Console.WriteLine(await result.Content.ReadAsStringAsync());
|
Console.WriteLine(await result.Content.ReadAsStringAsync());
|
||||||
var records = System.Text.Json.JsonSerializer.Deserialize<RecordList>(await result.Content.ReadAsStringAsync());
|
var records = JsonSerializer.Deserialize<RecordList>(await result.Content.ReadAsStringAsync());
|
||||||
return records!.Result[0];
|
return records!;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -76,22 +90,23 @@ public class AutoIpUpdateService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task UpdateDnsIpAsync(IConfiguration config, AAAARecord record, string ip)
|
private static async Task UpdateDnsIpAsync(IConfiguration config, DnsRecord record, string ip)
|
||||||
{
|
{
|
||||||
HttpClient cfClient = new();
|
HttpClient cfClient = new();
|
||||||
cfClient.DefaultRequestHeaders.Add("X-Auth-Email", config["cfEmail"]);
|
cfClient.DefaultRequestHeaders.Add("X-Auth-Email", config["cfEmail"]);
|
||||||
cfClient.DefaultRequestHeaders.Add("X-Auth-Key", config["cfKey"]);
|
cfClient.DefaultRequestHeaders.Add("X-Auth-Key", config["cfKey"]);
|
||||||
object content = new
|
|
||||||
|
object updateRequestBody = new
|
||||||
{
|
{
|
||||||
comment = "Update as needed",
|
name = record.Name,
|
||||||
|
ttl = record.Ttl,
|
||||||
|
type = record.Type,
|
||||||
|
comment = record.Comment,
|
||||||
content = ip,
|
content = ip,
|
||||||
name = "@",
|
proxied = record.Proxied,
|
||||||
proxied = true,
|
|
||||||
ttl = 3600,
|
|
||||||
type = "AAAA"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = await cfClient.PutAsJsonAsync(@$"https://api.cloudflare.com/client/v4/zones/{config["zoneId"]}/dns_records{record.Id}", content);
|
var result = await cfClient.PatchAsJsonAsync(@$"https://api.cloudflare.com/client/v4/zones/{config["zoneId"]}/dns_records/{record.Id}", updateRequestBody);
|
||||||
|
|
||||||
if (!result.IsSuccessStatusCode)
|
if (!result.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
@@ -102,11 +117,23 @@ public class AutoIpUpdateService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
record AAAARecord(
|
record DnsRecord(
|
||||||
|
[property: JsonPropertyName("name")] string Name,
|
||||||
|
[property: JsonPropertyName("ttl")] int Ttl,
|
||||||
|
[property: JsonPropertyName("type")] string Type,
|
||||||
[property: JsonPropertyName("comment")] string Comment,
|
[property: JsonPropertyName("comment")] string Comment,
|
||||||
[property: JsonPropertyName("content")] string Content,
|
[property: JsonPropertyName("content")] string Content,
|
||||||
[property: JsonPropertyName("name")] string Name,
|
[property: JsonPropertyName("proxied")] bool Proxied,
|
||||||
[property: JsonPropertyName("id")] string Id);
|
[property: JsonPropertyName("id")] string Id);
|
||||||
|
|
||||||
record RecordList([property: JsonPropertyName("result")] List<AAAARecord> Result);
|
record RecordList([property: JsonPropertyName("result")] List<DnsRecord> Records)
|
||||||
|
{
|
||||||
|
public DnsRecord this[int index]
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Records[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ public class ChessService : IChessService
|
|||||||
var piece = gameState.Pieces.FirstOrDefault(p => p.Id == pieceId);
|
var piece = gameState.Pieces.FirstOrDefault(p => p.Id == pieceId);
|
||||||
|
|
||||||
if (piece == null) return [];
|
if (piece == null) return [];
|
||||||
if (piece.Color != gameState.CurrentPlayer) return [];
|
|
||||||
|
|
||||||
var candidateMoves = GenerateCandidateMoves(gameState, piece);
|
var candidateMoves = GenerateCandidateMoves(gameState, piece);
|
||||||
var legalMoves = new List<Position>();
|
var legalMoves = new List<Position>();
|
||||||
@@ -529,7 +528,14 @@ public class ChessService : IChessService
|
|||||||
var ep = gs.EnPassantTarget.Value;
|
var ep = gs.EnPassantTarget.Value;
|
||||||
|
|
||||||
if (ep.Row == forward1 && Math.Abs(ep.Col - startCol) == 1)
|
if (ep.Row == forward1 && Math.Abs(ep.Col - startCol) == 1)
|
||||||
moves.Add(ep);
|
{
|
||||||
|
// Verify there's an enemy pawn to capture
|
||||||
|
int enemyPawnRow = piece.Color == PieceColor.White ? ep.Row + 1 : ep.Row - 1;
|
||||||
|
var enemyPawn = gs.Board[enemyPawnRow, ep.Col];
|
||||||
|
|
||||||
|
if (enemyPawn != null && enemyPawn.Type == PieceType.Pawn && enemyPawn.Color != piece.Color)
|
||||||
|
moves.Add(ep);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return moves;
|
return moves;
|
||||||
@@ -542,11 +548,7 @@ public class ChessService : IChessService
|
|||||||
=> GenerateSlidingMoves(gs, piece, [(1, 1), (1, -1), (-1, 1), (-1, -1)]);
|
=> GenerateSlidingMoves(gs, piece, [(1, 1), (1, -1), (-1, 1), (-1, -1)]);
|
||||||
|
|
||||||
private static List<Position> GenerateQueenMoves(GameState gs, ChessPiece piece)
|
private static List<Position> GenerateQueenMoves(GameState gs, ChessPiece piece)
|
||||||
=> GenerateSlidingMoves(gs, piece,
|
=> [..GenerateRookMoves(gs, piece), ..GenerateBishopMoves(gs, piece)];
|
||||||
[
|
|
||||||
(1, 0), (-1, 0), (0, 1), (0, -1),
|
|
||||||
(1, 1), (1, -1), (-1, 1), (-1, -1)
|
|
||||||
]);
|
|
||||||
|
|
||||||
private static List<Position> GenerateSlidingMoves(GameState gs, ChessPiece piece, (int dr, int dc)[] directions)
|
private static List<Position> GenerateSlidingMoves(GameState gs, ChessPiece piece, (int dr, int dc)[] directions)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
#chessBoard {
|
#boardContainer {
|
||||||
|
position: relative;
|
||||||
|
width: fit-content;
|
||||||
margin: 2vw auto;
|
margin: 2vw auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#chessBoard {
|
||||||
width: 60vw;
|
width: 60vw;
|
||||||
height: 60vw;
|
height: 60vw;
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -159,3 +164,29 @@
|
|||||||
height: 50px;
|
height: 50px;
|
||||||
pointer-events: none; /* ensures img doesn't steal the click */
|
pointer-events: none; /* ensures img doesn't steal the click */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chessSquare .coordinate-label {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 1.2vw;
|
||||||
|
font-weight: bold;
|
||||||
|
pointer-events: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chessSquare.light .coordinate-label {
|
||||||
|
color: #656770;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chessSquare.dark .coordinate-label {
|
||||||
|
color: #ccc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chessSquare .row-label {
|
||||||
|
top: 2px;
|
||||||
|
left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chessSquare .col-label {
|
||||||
|
bottom: 2px;
|
||||||
|
right: 4px;
|
||||||
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 108 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 132 KiB |
@@ -27,9 +27,14 @@ const ChessAPI = {
|
|||||||
body: JSON.stringify(moveDto)
|
body: JSON.stringify(moveDto)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
let message = await response.text();
|
||||||
|
throw new Error(message || "Invalid move or not your turn.");
|
||||||
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
|
|
||||||
if (!response.ok || !result.success) {
|
if (!result.success) {
|
||||||
throw new Error(result.message || "Invalid move or not your turn.");
|
throw new Error(result.message || "Invalid move or not your turn.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
const ChessBoard = {
|
const ChessBoard = {
|
||||||
renderPieces(pieces) {
|
renderPieces(pieces) {
|
||||||
this.clearAllSquares();
|
this.clearAllSquares();
|
||||||
|
this.renderCoordinateLabels();
|
||||||
this.placePieces(pieces);
|
this.placePieces(pieces);
|
||||||
this.setupSquareEventHandlers();
|
this.setupSquareEventHandlers();
|
||||||
this.highlightPreviousMove();
|
this.highlightPreviousMove();
|
||||||
@@ -106,6 +107,39 @@ const ChessBoard = {
|
|||||||
ChessAPI.handleMove(targetRow, targetCol);
|
ChessAPI.handleMove(targetRow, targetCol);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
renderCoordinateLabels() {
|
||||||
|
const files = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
|
||||||
|
const ranks = ['8', '7', '6', '5', '4', '3', '2', '1'];
|
||||||
|
|
||||||
|
// If player is black, reverse the coordinates
|
||||||
|
if (!GameState.currentPlayerIsWhite) {
|
||||||
|
files.reverse();
|
||||||
|
ranks.reverse();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < 64; i++) {
|
||||||
|
const square = document.getElementById(`square-${i}`);
|
||||||
|
const row = Math.floor(i / 8);
|
||||||
|
const col = i % 8;
|
||||||
|
|
||||||
|
// Add rank label (1-8) on the leftmost column
|
||||||
|
if (col === 0) {
|
||||||
|
const rankLabel = document.createElement('span');
|
||||||
|
rankLabel.className = 'coordinate-label row-label';
|
||||||
|
rankLabel.textContent = ranks[row];
|
||||||
|
square.appendChild(rankLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add file label (a-h) on the bottom row
|
||||||
|
if (row === 7) {
|
||||||
|
const fileLabel = document.createElement('span');
|
||||||
|
fileLabel.className = 'coordinate-label col-label';
|
||||||
|
fileLabel.textContent = files[col];
|
||||||
|
square.appendChild(fileLabel);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -3,5 +3,5 @@
|
|||||||
element.style.display = "none"; // Trigger reflow
|
element.style.display = "none"; // Trigger reflow
|
||||||
element.offsetHeight; // Force reflow
|
element.offsetHeight; // Force reflow
|
||||||
element.style.display = ""; // Restore the original display
|
element.style.display = ""; // Restore the original display
|
||||||
typeText("#WelcomeMessage", "Hey, you found me...!?``````````````````````No that's stupid````````````````Just````uhhhh`````Hello! My name is Josh. I'm a software engineer, and I try to have a lot of fun with what I do. While I have your attention, why don't I tell you more about myself?");
|
typeText("#WelcomeMessage", "Hey, you found me...!?``````````````````````No that's stupid...```````````````````Just...```````uhhhh...````````Hello! My name is Josh. I'm a software engineer, and I try to have a lot of fun with what I do. While I have your attention, why don't I tell you more about myself?");
|
||||||
}
|
}
|
||||||
@@ -12,18 +12,19 @@
|
|||||||
let intervalId;
|
let intervalId;
|
||||||
let index = 0;
|
let index = 0;
|
||||||
const punctuation = ['.', '!', '?'];
|
const punctuation = ['.', '!', '?'];
|
||||||
|
const punctuationDict = {
|
||||||
|
'.': 150,
|
||||||
|
'!': 200,
|
||||||
|
'?': 200,
|
||||||
|
',': 100,
|
||||||
|
'`': 25,
|
||||||
|
};
|
||||||
|
|
||||||
let currentText = "";
|
let currentText = "";
|
||||||
|
|
||||||
function changeIntervalTime(element, text) {
|
function changeIntervalTime(element, text) {
|
||||||
if (punctuation.includes(text.charAt(index))) {
|
clearInterval(intervalId);
|
||||||
clearInterval(intervalId);
|
simulateTyping(element, text, punctuationDict[text.charAt(index)] ?? 50);
|
||||||
simulateTyping(element, text, 200);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
clearInterval(intervalId);
|
|
||||||
simulateTyping(element, text);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function simulateTyping(element, text, speed = 50) {
|
function simulateTyping(element, text, speed = 50) {
|
||||||
|
|||||||
Reference in New Issue
Block a user