├── src ├── blagario │ ├── Pages │ │ ├── _Imports.razor │ │ ├── _Host.cshtml │ │ ├── Index.razor │ │ └── Index.cs │ ├── Shared │ │ └── MainLayout.razor │ ├── wwwroot │ │ ├── favicon.ico │ │ └── css │ │ │ └── site.css │ ├── appsettings.Development.json │ ├── _Imports.razor │ ├── blagario.csproj │ ├── appsettings.json │ ├── App.razor │ ├── Data │ │ ├── WeatherForecast.cs │ │ └── WeatherForecastService.cs │ ├── AgarElements │ │ ├── W.cs │ │ ├── Universe.cs │ │ ├── Virus.cs │ │ ├── Pellet.cs │ │ ├── TimerService.cs │ │ ├── AgarElement.cs │ │ ├── Cell.cs │ │ ├── CellPart.cs │ │ ├── ElementsHelper.cs │ │ ├── MoveableAgarElement.cs │ │ ├── Player.cs │ │ └── World.cs │ ├── Program.cs │ └── Startup.cs ├── global.json ├── blagario.Unit.Tests │ ├── blagario.Unit.Tests.csproj │ ├── SplitTests.cs │ ├── PushTests.cs │ └── DistanceEatabilityTests.cs └── blagario.sln ├── screenshots ├── blagario.gif ├── blagario_v0.gif └── blagario_v1.gif ├── LICENSE ├── README.md └── .gitignore /src/blagario/Pages/_Imports.razor: -------------------------------------------------------------------------------- 1 | @layout MainLayout 2 | -------------------------------------------------------------------------------- /src/global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "3.0.100" 4 | } 5 | } -------------------------------------------------------------------------------- /src/blagario/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 | @Body 4 | -------------------------------------------------------------------------------- /screenshots/blagario.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrl-alt-d/Blagario/HEAD/screenshots/blagario.gif -------------------------------------------------------------------------------- /screenshots/blagario_v0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrl-alt-d/Blagario/HEAD/screenshots/blagario_v0.gif -------------------------------------------------------------------------------- /screenshots/blagario_v1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrl-alt-d/Blagario/HEAD/screenshots/blagario_v1.gif -------------------------------------------------------------------------------- /src/blagario/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctrl-alt-d/Blagario/HEAD/src/blagario/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/blagario/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/blagario/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Components.Forms 3 | @using Microsoft.AspNetCore.Components.Routing 4 | @using Microsoft.JSInterop 5 | @using blagario 6 | @using blagario.Shared 7 | -------------------------------------------------------------------------------- /src/blagario/blagario.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | 7.3 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/blagario/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /src/blagario/App.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Page not found

7 |

Sorry, but there's nothing here!

8 |
9 |
-------------------------------------------------------------------------------- /src/blagario/Data/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace blagario.Data 4 | { 5 | public class WeatherForecast 6 | { 7 | public DateTime Date { get; set; } 8 | 9 | public long TemperatureC { get; set; } 10 | 11 | public long TemperatureF => 32 + (int)(TemperatureC / 0.5556); 12 | 13 | public string Summary { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/blagario.Unit.Tests/blagario.Unit.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/blagario/AgarElements/W.cs: -------------------------------------------------------------------------------- 1 | namespace blagario.elements 2 | { 3 | public class W: MoveableAgarElement 4 | { 5 | private W(Universe universe, long x, long y, long px, long py) 6 | { 7 | this.Universe = universe; 8 | this._Mass = 5; 9 | this.ElementType = ElementType.W; 10 | this.Vel = 0.3; 11 | this.X = x; 12 | this.Y = y; 13 | this.PointTo(px, py); 14 | } 15 | 16 | internal static W CreateW(Universe universe, long x, long y, long vx, long vy) 17 | { 18 | return new W(universe,x,y,vx,vy); 19 | } 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/blagario/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Hosting; 10 | using Microsoft.Extensions.Logging; 11 | 12 | namespace blagario 13 | { 14 | public class Program 15 | { 16 | public static void Main(string[] args) 17 | { 18 | CreateHostBuilder(args).Build().Run(); 19 | } 20 | 21 | public static IHostBuilder CreateHostBuilder(string[] args) => 22 | Host.CreateDefaultBuilder(args) 23 | .ConfigureWebHostDefaults(webBuilder => 24 | { 25 | webBuilder.UseStartup(); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/blagario/AgarElements/Universe.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace blagario.elements 7 | { 8 | public class Universe : AgarElement 9 | { 10 | public List Elements; 11 | public World World { get; private set;} 12 | public long MouseX {get; set; } 13 | public long MouseY {get; set; } 14 | 15 | public override string CssStyle( Player c ) =>$@" 16 | top: 0px ; 17 | left: 0px; 18 | width: 100%; 19 | height: 100%; 20 | "; 21 | 22 | public Universe() 23 | { 24 | this.X = long.MaxValue; 25 | this.Y = long.MaxValue; 26 | Elements = new List(); 27 | World = new World(this); 28 | } 29 | 30 | 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/blagario/AgarElements/Virus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | 5 | namespace blagario.elements 6 | { 7 | public class Virus : AgarElement 8 | { 9 | private Virus(Universe universe, long x, long y) 10 | { 11 | this.Universe = universe; 12 | this._Mass = 100; 13 | this.ElementType = ElementType.Virus; 14 | this.X = x; 15 | this.Y = y; 16 | } 17 | 18 | internal static Virus CreateVirus(Universe universe) 19 | { 20 | var goodPlaceForX = getrandom.Next(0,(int)universe.World.X); 21 | var goodPlaceForY = getrandom.Next(0,(int)universe.World.Y); 22 | 23 | var v = new Virus(universe, goodPlaceForX, goodPlaceForY); 24 | universe.World.Elements.Add(v); 25 | return v; 26 | } 27 | 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/blagario/Data/WeatherForecastService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | 5 | namespace blagario.Data 6 | { 7 | public class WeatherForecastService 8 | { 9 | private static string[] Summaries = new[] 10 | { 11 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 12 | }; 13 | 14 | public Task GetForecastAsync(DateTime startDate) 15 | { 16 | var rng = new Random(); 17 | return Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast 18 | { 19 | Date = startDate.AddDays(index), 20 | TemperatureC = rng.Next(-20, 55), 21 | Summary = Summaries[rng.Next(Summaries.Length)] 22 | }).ToArray()); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 dani herrera 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blagario project 2 | 3 | This is just an experimental lab to test to make a multiplayer web game without javascript (or almost without). Components: 4 | 5 | * blazor ( netcore 3.0.100 ) 6 | * html 7 | * css 8 | 9 | Maybe a more simple game (pong, snake, space invaders ) would be enough to make the lab ... but ... let's try. 10 | 11 | This is not a real project, just a lab to enjoy and for experimental purposes (check performance, refresh, network bandwidth, learn new blazor features, ... ). 12 | 13 | ### How it works? 14 | 15 | * All elements ( cell, viruses, world, pellets, W) are simple C# classes. 16 | * Each element has a `Tic` method who makes game move on. 17 | * They are a `HostedService` who calls the `Tic`s. 18 | * `Universe` is injected as `AddSingleton`: one Universe for all people. 19 | * `Player` is injected as `Scoped`: one Cell for Player (connection). 20 | * Mouse is tracked by blazor ( `@onmousemove`'s `MouseEventArgs` ) 21 | 22 | ### Collaboration wanted: 23 | 24 | [See todo list and make PR's](https://github.com/ctrl-alt-d/Blagario/issues/1) 25 | 26 | Make PR to this report and contribute to [hacktoberfest](https://hacktoberfest.digitalocean.com/) 27 | 28 | ![screenshot](./screenshots/blagario_v1.gif) 29 | *Two gamers, two cells* 30 | -------------------------------------------------------------------------------- /src/blagario/AgarElements/Pellet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | 5 | namespace blagario.elements 6 | { 7 | public class Pellet : AgarElement 8 | { 9 | private Pellet(Universe universe, long x, long y) 10 | { 11 | this.Universe = universe; 12 | this._Mass = 1; 13 | this.ElementType = ElementType.Pellet; 14 | this.X = x; 15 | this.Y = y; 16 | MyColor = availableColors[ getrandom.Next(0, availableColors.Length) ]; 17 | 18 | } 19 | static string[] availableColors = new string [] {"2ecc71", "3498db", "9b59b6", "f1c40f", "e67e22", "e74c3c" }; 20 | public string MyColor; 21 | public override string CssStyle(Player c) => base.CssStyle(c) + $@"background-color: #{MyColor}"; 22 | 23 | internal static Pellet CreatePellet(Universe universe) 24 | { 25 | var goodPlaceForX = getrandom.Next(0,(int)universe.World.X); 26 | var goodPlaceForY = getrandom.Next(0,(int)universe.World.Y); 27 | 28 | var v = new Pellet(universe, goodPlaceForX, goodPlaceForY); 29 | universe.World.Elements.Add(v); 30 | return v; 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/blagario/Pages/_Host.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @namespace blagario.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | 5 | 6 | 7 | 8 | 9 | 10 | blagario 11 | 12 | 13 | 14 | 15 | @(await Html.RenderComponentAsync(RenderMode.Server)) 16 | 17 | 18 | 19 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/blagario.Unit.Tests/SplitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | using blagario.elements; 4 | using System.Threading.Tasks; 5 | using System.Linq; 6 | 7 | namespace blagario.Unit.Tests 8 | { 9 | public class SplitTests 10 | { 11 | 12 | private double MassFromRadius(int radius) => Math.Pow( radius,2 )*Math.PI; 13 | 14 | [Fact] 15 | public async Task CheckSplit() 16 | { 17 | /* 18 | 19 | |<-----1000mass->| 20 | o----------------) 21 | 22 | */ 23 | 24 | // taken 2 closed cells with 0 speed. 25 | var universe = new Universe(); 26 | var cell = new Cell(universe); 27 | var one = new CellPart(universe, cell) { 28 | X=500, 29 | Y=100, 30 | _Mass=1000, 31 | }; 32 | cell.PointTo(0,0); 33 | cell.Add(one); 34 | 35 | // After Split for 4 times 36 | cell.Split(); 37 | await Task.WhenAll( cell.Select(c=>c.Tic(0) ) ); 38 | cell.Split(); 39 | await Task.WhenAll( cell.Select(c=>c.Tic(0) ) ); 40 | cell.Split(); 41 | await Task.WhenAll( cell.Select(c=>c.Tic(0) ) ); 42 | 43 | // Distance should increase 44 | Assert.Equal(8, cell.Count ); 45 | Assert.All( cell, c=> Assert.True(c.X > 300 && c.X < 505) ); 46 | } 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/blagario.Unit.Tests/PushTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | using blagario.elements; 4 | using System.Threading.Tasks; 5 | 6 | namespace blagario.Unit.Tests 7 | { 8 | public class PushTests 9 | { 10 | 11 | private double MassFromRadius(int radius) => Math.Pow( radius,2 )*Math.PI; 12 | 13 | [Fact] 14 | public async Task OneCloserOther() 15 | { 16 | /* 17 | 18 | |<------15------>| 19 | |<---10--->| 20 | |<---10--->| 21 | o-----(----)-----o 22 | 23 | */ 24 | 25 | // taken 2 closed cells with 0 speed. 26 | var universe = new Universe(); 27 | var cell = new Cell(universe); 28 | var one = new CellPart(universe, cell) {X=500, Y=100, _Mass=MassFromRadius(10) } ; 29 | var other = new CellPart(universe, cell) {X=515, Y=100, _Mass=MassFromRadius(10) } ; 30 | var initialXdistance = Math.Abs( one.X - other.X); 31 | cell.Add(one); 32 | cell.Add(other); 33 | 34 | // After Tic 35 | await one.Tic(0); 36 | var finalXdistance1 = Math.Abs( one.X - other.X); 37 | await other.Tic(0); 38 | var finalXdistance2 = Math.Abs( one.X - other.X); 39 | 40 | // Distance should increase 41 | 42 | 43 | Assert.True( ElementsHelper.Collides(one, other) ); 44 | Assert.True( finalXdistance1 > initialXdistance ); 45 | Assert.True( finalXdistance2 > finalXdistance1 ); 46 | } 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/blagario/AgarElements/TimerService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Hosting; 5 | 6 | namespace blagario.elements 7 | { 8 | public static class TaskExtensions 9 | { 10 | public static void DoNotAwait(this Task task) { } 11 | } 12 | 13 | internal class TimedHostedService : IHostedService, IDisposable 14 | { 15 | //private Timer _timer; 16 | private World World; 17 | 18 | public bool IsRunning {get; private set;} 19 | public const int fps = 60; 20 | public const int millisecondsdelay = 1000 / fps; 21 | 22 | public TimedHostedService(Universe universe) 23 | { 24 | World = universe.World; 25 | } 26 | 27 | public async Task StartAsync(CancellationToken cancellationToken) 28 | { 29 | IsRunning = true; 30 | //_timer = new Timer(DoWork, null, TimeSpan.Zero, 31 | // TimeSpan.FromMilliseconds(20)); 32 | 33 | Task.Run( async () => { 34 | int fpsTicNum = 0; 35 | while (IsRunning) 36 | { 37 | System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch(); 38 | stopWatch.Start(); 39 | await World.Tic(fpsTicNum); 40 | stopWatch.Stop(); 41 | var d = millisecondsdelay - stopWatch.Elapsed.Milliseconds; 42 | if (d<=1) d = 1; 43 | await Task.Delay(d); 44 | fpsTicNum = (fpsTicNum+1) % 60; 45 | } 46 | }).DoNotAwait(); 47 | 48 | await Task.CompletedTask; 49 | } 50 | 51 | public Task StopAsync(CancellationToken cancellationToken) 52 | { 53 | IsRunning = false; 54 | return Task.CompletedTask; 55 | } 56 | 57 | public void Dispose() 58 | { 59 | //_timer?.Dispose(); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/blagario/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @inherits BaseIndex 3 | @using Microsoft.AspNetCore.Components.Web 4 | 5 | 6 |
16 | 17 |
22 | 23 | @foreach (var element in VisibleElements ) 24 | { 25 |
29 |
30 | } 31 |
32 | 33 | 34 |
35 | #BLAGARIO 36 |
37 | 38 | 39 |
40 |
    41 | @foreach( var cell in Universe.World.Leaderboard) 42 | { 43 |
  • @cell.Name @cell.Mass.ToString()
  • 44 | } 45 |
46 |
47 | 48 | 49 | @if ( Player.Cell.IsDead ) 50 | { 51 |
52 | 53 | 54 |
55 | } 56 | 57 |
58 | -------------------------------------------------------------------------------- /src/blagario/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; 3 | } 4 | 5 | .universe { 6 | position: fixed; 7 | background-color: rgb(15, 90, 15); 8 | margin: 0; 9 | padding: 0; 10 | border: 0; 11 | } 12 | 13 | .scoreboard { 14 | position: fixed; 15 | background-color: rgba(0, 0, 0, 0.507); 16 | margin: 0; 17 | padding: 0; 18 | border: 1px solid rgba(255, 0, 0, 0.555); 19 | top: 10px; 20 | left: 10px; 21 | width: 100px; 22 | height: 40px; 23 | } 24 | 25 | .world { 26 | position: fixed; 27 | background-color: black; 28 | margin: 0; 29 | padding: 0; 30 | border: 1px solid red; 31 | } 32 | 33 | .agar-element { 34 | position: absolute; 35 | border-radius: 50%; 36 | background-color: orange; 37 | margin: 0; 38 | padding: 0; 39 | border: 0; 40 | } 41 | 42 | .cellpart { 43 | position: fixed; 44 | border-radius: 50%; 45 | background-color: orange; 46 | margin: 0; 47 | padding: 0; 48 | border: 0; 49 | } 50 | 51 | .virus { 52 | position: absolute; 53 | border-radius: 50%; 54 | width: 100px; 55 | height: 100px; 56 | background: rgba(0, 128, 0, 0.671); 57 | margin: 0; 58 | padding: 0; 59 | border: 0; 60 | } 61 | 62 | .pellet { 63 | position: absolute; 64 | border-radius: 50%; 65 | width: 1px; 66 | height: 1px; 67 | background: green; 68 | margin: 0; 69 | padding: 0; 70 | border: 0; 71 | } 72 | 73 | .leaderboard { 74 | position: fixed; 75 | background-color:#00000081; 76 | margin: 0; 77 | padding: 0; 78 | border: 1px solid rgba(255, 0, 0, 0.644); 79 | width: 200px; 80 | top: 10px; 81 | right: 10px; 82 | height: 300px; 83 | } 84 | 85 | .leaderboard ul { 86 | padding: 14px; 87 | margin: 0; 88 | color: white; 89 | } 90 | 91 | .respawn { 92 | position: fixed; 93 | top: 50%; 94 | left: 50%; 95 | } 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /src/blagario/AgarElements/AgarElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace blagario.elements 5 | { 6 | public enum ElementType 7 | { 8 | Universe, 9 | World, 10 | Virus, 11 | Pellet, 12 | CellPart, 13 | W 14 | } 15 | public class AgarElement 16 | { 17 | protected static readonly Random getrandom = new Random(); 18 | private Guid key = Guid.NewGuid(); 19 | public string Key => key.ToString(); 20 | public ElementType ElementType {get; protected set; } 21 | public string Name {get; set;} = ""; 22 | public double X {set; get; } 23 | public double Y {set; get; } 24 | public double _Mass {set; get; } 25 | public double _EatedMass {set; get; } = 0; 26 | public virtual bool EatableByMySelf {set; get; } = false; 27 | public long Mass => (int)_Mass; 28 | public virtual long MaxMass {set; get; } = 20000; 29 | public virtual async Task Tic(int fpsTicNum) 30 | { 31 | double eat = 0; 32 | if ( _EatedMass>0 ) 33 | { 34 | eat = _Mass * 0.01; 35 | eat = (eat>_EatedMass) ? _EatedMass : eat; 36 | } 37 | 38 | _EatedMass -= eat; 39 | _Mass += eat; 40 | await Task.CompletedTask; 41 | } 42 | public virtual double Radius => ElementsHelper.GetRadiusFromMass(this.Mass); 43 | 44 | public virtual double Diameter => Radius * 2; 45 | public long CssX =>ElementsHelper.TryConvert(X-Radius); 46 | public long CssY =>ElementsHelper.TryConvert(Y-Radius); 47 | public Universe Universe {get; protected set;} 48 | public string CssClass => this.GetType().Name.ToLower(); 49 | public virtual string CssStyle( Player c) => $@" 50 | top: {(c.YGame2World(CssY)).ToString()}px ; 51 | left: {(c.XGame2World(CssX)).ToString()}px ; 52 | width: {(ElementsHelper.TryConvert(Diameter * c.Zoom)).ToString()}px ; 53 | height: {(ElementsHelper.TryConvert(Diameter * c.Zoom)).ToString()}px ; 54 | "; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/blagario/AgarElements/Cell.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace blagario.elements 6 | { 7 | public class Cell: List 8 | { 9 | public Cell(Universe universe) 10 | { 11 | this.Universe = universe; 12 | } 13 | public double? TotalMass => this.Select(c => c._Mass).Sum(); 14 | public double? Diameter => this.Select(c => c.Diameter).Sum(); 15 | public double? X => this.FirstOrDefault()?.X; 16 | public double? Y => this.FirstOrDefault()?.Y; 17 | public void PointTo(double bx, double by) => this.ForEach(c => c.PointTo(bx, by) ); 18 | public void Purge() => this.RemoveAll(x=>x._Mass == 0); 19 | public bool IsDead => (TotalMass??0) == 0; 20 | public bool CurrentlySplitted => this.Count()>=2; 21 | public IEnumerable SplittableParts => this.Where(e=>e._Mass >= 35 ).ToList(); 22 | public bool CanSplit => this.Count() <= 12 && SplittableParts.Any(); 23 | public Universe Universe {set; get;} 24 | public string Name => this.FirstOrDefault()?.Name; 25 | public string MyColor => this.FirstOrDefault()?.MyColor; 26 | 27 | public void Split() 28 | { 29 | if (IsDead) return; 30 | var newParts = new List(); 31 | foreach( var currentPart in this.SplittableParts) 32 | { 33 | //if (newParts.Count + this.Count >= 16 ) break; 34 | currentPart._Mass /= 2; 35 | var newPart = new CellPart(Universe, currentPart.Cell); 36 | newPart.Name =this.Name; 37 | newPart.MyColor = this.MyColor; 38 | var deltaX = 0.1 * ( newPart.PointingXto > newPart.X ? 1 : -1 ); 39 | var deltaY = 0.1 * ( newPart.PointingYto > newPart.Y ? 1 : -1 ); 40 | newPart.X = currentPart.X+deltaX; 41 | newPart.Y = currentPart.Y+deltaY; 42 | newPart._Mass = currentPart._Mass; 43 | newPart.ChangeVelToSplitVel(); 44 | newPart.PointTo(currentPart.PointingXto, currentPart.PointingYto); 45 | newParts.Add(newPart); 46 | } 47 | this.AddRange( newParts ); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/blagario/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Components; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.AspNetCore.HttpsPolicy; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Hosting; 12 | using blagario.elements; 13 | 14 | namespace blagario 15 | { 16 | public class Startup 17 | { 18 | public Startup(IConfiguration configuration) 19 | { 20 | Configuration = configuration; 21 | } 22 | 23 | public IConfiguration Configuration { get; } 24 | 25 | // This method gets called by the runtime. Use this method to add services to the container. 26 | // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 27 | public void ConfigureServices(IServiceCollection services) 28 | { 29 | services.AddRazorPages(); 30 | services.AddServerSideBlazor(); 31 | services.AddSingleton(); 32 | services.AddScoped(); 33 | services.AddHostedService(); 34 | } 35 | 36 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 37 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 38 | { 39 | if (env.IsDevelopment()) 40 | { 41 | app.UseDeveloperExceptionPage(); 42 | } 43 | else 44 | { 45 | app.UseExceptionHandler("/Home/Error"); 46 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 47 | app.UseHsts(); 48 | } 49 | 50 | //app.UseHttpsRedirection(); 51 | app.UseStaticFiles(); 52 | 53 | app.UseRouting(); 54 | 55 | app.UseEndpoints(endpoints => 56 | { 57 | endpoints.MapBlazorHub(); 58 | endpoints.MapFallbackToPage("/_Host"); 59 | }); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/blagario/AgarElements/CellPart.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | 4 | namespace blagario.elements 5 | { 6 | public class CellPart: MoveableAgarElement 7 | { 8 | public CellPart(Universe universe, Cell cell) 9 | { 10 | this.Universe = universe; 11 | this._Mass = 542; //ToDo: move to 17 some day. 12 | this.ElementType = ElementType.CellPart; 13 | var goodPlaceForX = getrandom.Next(0,(int)universe.World.X); 14 | var goodPlaceForY = getrandom.Next(0,(int)universe.World.Y); 15 | this.X = goodPlaceForX; 16 | this.Y = goodPlaceForY; 17 | lock(this.Universe.World.Elements) Universe.World.Elements.Add(this); 18 | MyColor = availableColors[ getrandom.Next(0, availableColors.Length) ]; 19 | Cell = cell; 20 | } 21 | public Cell Cell {set; get;} 22 | public override double VelBase => 0.2 ; 23 | public override double Vel => 0.2; 24 | public void ChangeVelToSplitVel() => this.Vel = 0.25; 25 | 26 | public string MyColor; 27 | 28 | public override async Task Tic(int fpsTicNum) { 29 | 30 | // passive loss of mass 31 | _Mass = _Mass * 0.999995; 32 | if (_Mass>0 && _Mass<10) _Mass = 10; 33 | 34 | // go from Vel to VelBase 35 | var diffVelVelBase = (Vel-VelBase)*0.01; 36 | //Vel -= diffVelVelBase; 37 | 38 | // collides with other cellparts in same cell? 39 | var collisions = this 40 | .Cell 41 | .Where(c=>c!=this) 42 | .Where(c=> ElementsHelper.Collides(this,c) ) 43 | .ToList(); 44 | foreach( var otherCells in collisions ) 45 | { 46 | this.PushTo( 2*X-otherCells.X , 2*Y-otherCells.Y, 2 ); 47 | } 48 | 49 | await base.Tic(fpsTicNum); 50 | } 51 | 52 | static string[] availableColors = new string [] {"2ecc71", "3498db", "9b59b6", "f1c40f", "e67e22", "e74c3c" }; 53 | 54 | private string pepaCss => 55 | Name=="Pepa" 56 | ?$@" 57 | background-image:url('https://i.imgur.com/ZUbWYDl.jpg'); 58 | background-repeat: no-repeat; 59 | background-size: 100% 100%;" 60 | :$"background-color: #{MyColor};"; 61 | 62 | 63 | public override string CssStyle(Player c) => 64 | c.Cell.IsDead 65 | ?"visibility:none" 66 | :base.CssStyle(c) 67 | +$@"position: absolute;" 68 | +pepaCss; 69 | 70 | 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/blagario/AgarElements/ElementsHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace blagario.elements 5 | { 6 | public static class ElementsHelper 7 | { 8 | private static double?[] RadiusFromMassCache = new double?[22001]; 9 | public static double GetRadiusFromMass(long mass) 10 | { 11 | var r = RadiusFromMassCache[mass]; 12 | if (r==null) 13 | { 14 | r = Math.Sqrt( mass / Math.PI ); 15 | RadiusFromMassCache[mass] = r; 16 | } 17 | return r.Value; 18 | } 19 | 20 | public static double TwoElementsCenterDistance( AgarElement one, AgarElement other ) => 21 | Math.Sqrt( 22 | Math.Pow(one.X - other.X, 2) + 23 | Math.Pow(one.Y - other.Y, 2) 24 | ); 25 | 26 | public static bool CanOneElementEatsOtherOneByDistance(AgarElement oneElement, AgarElement otherElement) 27 | { 28 | var r = ElementsHelper.TwoElementsCenterDistance(oneElement, otherElement) + otherElement.Radius * 0.4 < oneElement.Radius; 29 | return r; 30 | } 31 | 32 | public static bool Collides(AgarElement oneElement, AgarElement otherElement) 33 | { 34 | var r = ElementsHelper.TwoElementsCenterDistance(oneElement, otherElement) <= oneElement.Radius + otherElement.Radius; 35 | return r; 36 | } 37 | 38 | public static bool CanOneElementEatsOtherOneByMass(AgarElement oneElement, AgarElement otherElement) 39 | { 40 | var r = oneElement._Mass * 0.9 > otherElement._Mass; 41 | return r; 42 | } 43 | 44 | public static bool CanOneElementEatsOtherOneByCellGroup(AgarElement oneElement, AgarElement otherElement) 45 | { 46 | var bothAreCellParts = oneElement.ElementType != ElementType.CellPart && otherElement.ElementType != ElementType.CellPart; 47 | if (!bothAreCellParts) return true; 48 | 49 | var oneCellPartElement = oneElement as CellPart; 50 | var otherCellPartElement = otherElement as CellPart; 51 | 52 | var sameCell = oneCellPartElement.Cell == otherCellPartElement.Cell; 53 | if (!sameCell) return true; 54 | 55 | var bothEatables = oneCellPartElement.EatableByMySelf && otherCellPartElement.EatableByMySelf; 56 | return bothEatables; 57 | 58 | 59 | } 60 | 61 | public static Int64 TryConvert(object n) 62 | { 63 | try{ 64 | return Convert.ToInt64(n); 65 | } 66 | catch 67 | { 68 | return 0; 69 | } 70 | 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/blagario/Pages/Index.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using blagario.elements; 6 | using Microsoft.AspNetCore.Components; 7 | using Microsoft.AspNetCore.Components.Web; 8 | using Microsoft.JSInterop; 9 | namespace blagario 10 | { 11 | public abstract class BaseIndex : ComponentBase, IDisposable 12 | { 13 | [Inject] protected Universe Universe {get; set; } 14 | [Inject] protected Player Player {get; set; } 15 | [Inject] protected IJSRuntime JsRuntime {get; set; } 16 | 17 | public void Dispose() { 18 | Universe.World.OnTicReached -= UpdateUi; 19 | } 20 | 21 | protected List VisibleElements = new List(); 22 | 23 | protected override void OnInitialized() 24 | { 25 | Universe.World.OnTicReached += UpdateUi; 26 | } 27 | private void UpdateUi(object sender, EventArgs ea) 28 | { 29 | InvokeAsync( 30 | () => 31 | { 32 | VisibleElements = Universe 33 | .World 34 | .Elements 35 | .Where( e=> Player.OnArea(e) ) 36 | .ToList(); 37 | StateHasChanged(); 38 | }); 39 | } 40 | 41 | protected void TrackMouse(MouseEventArgs e) 42 | { 43 | var bx = Player.XPysics2Game(ElementsHelper.TryConvert(e.ClientX)); 44 | var by = Player.YPysics2Game(ElementsHelper.TryConvert(e.ClientY)); 45 | Player.PointTo( bx, by); 46 | } 47 | 48 | protected void MouseWheel(WheelEventArgs e) 49 | { 50 | Player.IncreaseZoom( - (float)(e.DeltaY/100.0) ); 51 | } 52 | 53 | protected void KeyDown(KeyboardEventArgs e) 54 | { 55 | //Issue: KeyDown only is fired when input has focus. 56 | //System.Console.WriteLine( $"Presset: [{e.Key}]" ); 57 | switch (e.Key) 58 | { 59 | case " ": 60 | Player.Cell.Split(); 61 | break; 62 | } 63 | } 64 | 65 | protected void OnClick(MouseEventArgs e) 66 | { 67 | Player.Cell.Split(); 68 | } 69 | 70 | protected async override Task OnAfterRenderAsync(bool firstRender) 71 | { 72 | if (firstRender) 73 | { 74 | await OnAfterFirstRenderAsync(); 75 | } 76 | } 77 | protected async Task OnAfterFirstRenderAsync() 78 | { 79 | await Player.CheckVisibleArea(JsRuntime); 80 | await Player.SetFocusToUniverse(JsRuntime); 81 | } 82 | 83 | } 84 | } -------------------------------------------------------------------------------- /src/blagario.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "blagario", "blagario\blagario.csproj", "{663CEDAE-9322-4EAA-B681-9DB87145E579}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "blagario.Unit.Tests", "blagario.Unit.Tests\blagario.Unit.Tests.csproj", "{042C9CFF-837C-4EC2-B6FC-39CC84DC74BB}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {663CEDAE-9322-4EAA-B681-9DB87145E579}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {663CEDAE-9322-4EAA-B681-9DB87145E579}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {663CEDAE-9322-4EAA-B681-9DB87145E579}.Debug|x64.ActiveCfg = Debug|Any CPU 26 | {663CEDAE-9322-4EAA-B681-9DB87145E579}.Debug|x64.Build.0 = Debug|Any CPU 27 | {663CEDAE-9322-4EAA-B681-9DB87145E579}.Debug|x86.ActiveCfg = Debug|Any CPU 28 | {663CEDAE-9322-4EAA-B681-9DB87145E579}.Debug|x86.Build.0 = Debug|Any CPU 29 | {663CEDAE-9322-4EAA-B681-9DB87145E579}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {663CEDAE-9322-4EAA-B681-9DB87145E579}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {663CEDAE-9322-4EAA-B681-9DB87145E579}.Release|x64.ActiveCfg = Release|Any CPU 32 | {663CEDAE-9322-4EAA-B681-9DB87145E579}.Release|x64.Build.0 = Release|Any CPU 33 | {663CEDAE-9322-4EAA-B681-9DB87145E579}.Release|x86.ActiveCfg = Release|Any CPU 34 | {663CEDAE-9322-4EAA-B681-9DB87145E579}.Release|x86.Build.0 = Release|Any CPU 35 | {042C9CFF-837C-4EC2-B6FC-39CC84DC74BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {042C9CFF-837C-4EC2-B6FC-39CC84DC74BB}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {042C9CFF-837C-4EC2-B6FC-39CC84DC74BB}.Debug|x64.ActiveCfg = Debug|Any CPU 38 | {042C9CFF-837C-4EC2-B6FC-39CC84DC74BB}.Debug|x64.Build.0 = Debug|Any CPU 39 | {042C9CFF-837C-4EC2-B6FC-39CC84DC74BB}.Debug|x86.ActiveCfg = Debug|Any CPU 40 | {042C9CFF-837C-4EC2-B6FC-39CC84DC74BB}.Debug|x86.Build.0 = Debug|Any CPU 41 | {042C9CFF-837C-4EC2-B6FC-39CC84DC74BB}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {042C9CFF-837C-4EC2-B6FC-39CC84DC74BB}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {042C9CFF-837C-4EC2-B6FC-39CC84DC74BB}.Release|x64.ActiveCfg = Release|Any CPU 44 | {042C9CFF-837C-4EC2-B6FC-39CC84DC74BB}.Release|x64.Build.0 = Release|Any CPU 45 | {042C9CFF-837C-4EC2-B6FC-39CC84DC74BB}.Release|x86.ActiveCfg = Release|Any CPU 46 | {042C9CFF-837C-4EC2-B6FC-39CC84DC74BB}.Release|x86.Build.0 = Release|Any CPU 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /src/blagario/AgarElements/MoveableAgarElement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace blagario.elements 5 | { 6 | public class MoveableAgarElement: AgarElement 7 | { 8 | protected double Vx {get; private set;} = 0; 9 | protected double Vy {get; private set;} = 0; 10 | 11 | protected double VxPushed {get; private set;} = 0; 12 | protected double VyPushed {get; private set;} = 0; 13 | 14 | public double PointingXto {get; private set; } 15 | public double PointingYto {get; private set; } 16 | 17 | public virtual double Vel {get; protected set;} = 1; 18 | public virtual double VelBase {get; protected set;} = 1; 19 | 20 | public virtual void PointTo( double x, double y ) 21 | { 22 | 23 | /* o <---mouse (x,y) 24 | /| 25 | / | 26 | / |dy 27 | Vx / | 28 | ---o <--- new point 29 | | /| | 30 | |/ | Vy | 31 | cell--->o---------------- 32 | |---dx--| 33 | 34 | */ 35 | 36 | var dx = x - this.X; 37 | var dy = y - this.Y; 38 | var d = Math.Sqrt( dx*dx + dy*dy ); 39 | var sinAlf = dy / d; 40 | var cosAlf = dx / d; 41 | 42 | this.Vy = this.Vel * sinAlf; 43 | this.Vx = this.Vel * cosAlf; 44 | 45 | PointingXto = x; 46 | PointingYto = y; 47 | 48 | } 49 | 50 | 51 | public virtual void PushTo( double x, double y, double force ) 52 | { 53 | 54 | /* o <---mouse (x,y) 55 | /| 56 | / | 57 | / |dy 58 | Vx / | 59 | ---o <--- new point 60 | | /| | 61 | |/ | Vy | 62 | cell--->o---------------- 63 | |---dx--| 64 | 65 | */ 66 | 67 | var dx = x - this.X; 68 | var dy = y - this.Y; 69 | var d = Math.Sqrt( dx*dx + dy*dy ); 70 | var sinAlf = dy / d; 71 | var cosAlf = dx / d; 72 | 73 | this.VyPushed = this.VelBase * force * sinAlf; 74 | this.VxPushed = this.VelBase * force * cosAlf; 75 | 76 | } 77 | 78 | public override async Task Tic(int fpsTicNum) { 79 | this.X += Vx; 80 | this.Y += Vy; 81 | 82 | this.X = this.X > Universe.World.X?Universe.World.X:this.X; 83 | this.Y = this.Y > Universe.World.Y?Universe.World.Y:this.Y; 84 | 85 | this.X += VxPushed; 86 | this.Y += VyPushed; 87 | 88 | VxPushed = 0; 89 | VyPushed = 0; 90 | 91 | this.X = this.X < 0?0:this.X; 92 | this.Y = this.Y < 0?0:this.Y; 93 | 94 | await base.Tic(fpsTicNum); 95 | } 96 | 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/blagario.Unit.Tests/DistanceEatabilityTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | using blagario.elements; 4 | 5 | namespace blagario.Unit.Tests 6 | { 7 | public class DistanceEatabilityTests 8 | { 9 | 10 | private double MassFromRadius(int radius) => Math.Pow( radius,2 )*Math.PI; 11 | 12 | [Fact] 13 | public void OneContainsOther() 14 | { 15 | // taken 2 closed elements 16 | var one = new AgarElement() {X=500, Y=500, _Mass=MassFromRadius(11) } ; 17 | var other = new AgarElement() {X=500, Y=500, _Mass=MassFromRadius(9) } ; 18 | 19 | // When check for eatability 20 | var canEat = ElementsHelper.CanOneElementEatsOtherOneByDistance(one,other); 21 | 22 | // One must be able to eat the other one 23 | Assert.True(canEat); 24 | } 25 | 26 | [Fact] 27 | public void OneCloserOther() 28 | { 29 | /* 30 | 31 | |<---------15-------->| 32 | |<----10---->| 33 | o ---- (---- o ----)--) 34 | |<-2->| 35 | 36 | */ 37 | 38 | // taken 2 closed elements 39 | var one = new AgarElement() {X=500, Y=100, _Mass=MassFromRadius(15) } ; 40 | var other = new AgarElement() {X=510, Y=100, _Mass=MassFromRadius(2) } ; 41 | 42 | // When check for eatability 43 | var canEat = ElementsHelper.CanOneElementEatsOtherOneByDistance(one,other); 44 | 45 | // One must be able to eat the other one 46 | Assert.True(canEat); 47 | } 48 | 49 | 50 | [Fact] 51 | public void OneOutOfTheOther() 52 | { 53 | /* 54 | 55 | |<-----12------->| 56 | |<----10---->| 57 | o ---- (---- o --)-) 58 | |<-6->|<-6->| 59 | 60 | */ 61 | 62 | // taken 2 closed elements 63 | var one = new AgarElement() {X=500, Y=100, _Mass=MassFromRadius(12) }; 64 | var other = new AgarElement() {X=510, Y=100, _Mass=MassFromRadius(6) }; 65 | 66 | // When check for eatability 67 | var canEat = ElementsHelper.CanOneElementEatsOtherOneByDistance(one,other); 68 | 69 | // One must be unable to eat the other one 70 | Assert.False(canEat); 71 | 72 | } 73 | 74 | [Fact] 75 | public void OneCloserOtherDiagonal() 76 | { 77 | /* 78 | |-2-| 79 | -o 80 | | \ 81 | 2 \/ 82 | | /\ 83 | - o 84 | \/ 85 | / 86 | 87 | 88 | */ 89 | 90 | // taken 2 closed elements 91 | var one = new AgarElement() {X=500, Y=100, _Mass=MassFromRadius( (int)Math.Sqrt( 3*3 + 3*3 ) ) } ; 92 | var other = new AgarElement() {X=502, Y=102, _Mass=MassFromRadius(1) } ; 93 | 94 | // When check for eatability 95 | var canEat = ElementsHelper.CanOneElementEatsOtherOneByDistance(one,other); 96 | 97 | // One must be able to eat the other one 98 | Assert.True(canEat); 99 | } 100 | 101 | [Fact] 102 | public void OneFarAwayOtherDiagonal() 103 | { 104 | /* 105 | |-2-| 106 | -o 107 | | \ 108 | 2 \/ 109 | | /\ 110 | - o 111 | \/ 112 | / 113 | 114 | 115 | */ 116 | 117 | // taken 2 closed elements 118 | var one = new AgarElement() {X=500, Y=100, _Mass=MassFromRadius( (int)Math.Sqrt( 2*2 + 2*2 ) ) } ; 119 | var other = new AgarElement() {X=502, Y=102, _Mass=MassFromRadius(1) } ; 120 | 121 | // When check for eatability 122 | var canEat = ElementsHelper.CanOneElementEatsOtherOneByDistance(one,other); 123 | 124 | // One must be able to eat the other one 125 | Assert.False(canEat); 126 | } 127 | 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/blagario/AgarElements/Player.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using System.Timers; 4 | using Microsoft.AspNetCore.Components; 5 | using Microsoft.JSInterop; 6 | 7 | namespace blagario.elements 8 | { 9 | public class Player: IDisposable 10 | { 11 | public Universe Universe; 12 | public Player(Universe universe) 13 | { 14 | Universe = universe; 15 | Cell = new Cell(this.Universe); 16 | Universe.World.OnTicReached += OnTicEvent; 17 | } 18 | public string FormCss => $@" 19 | position: absolute; 20 | top: {XGame2World(CurrentX)}px; 21 | right: {XGame2World(CurrentY)}px; 22 | "; 23 | 24 | public string Name {set; get;} 25 | public void Play() 26 | { 27 | 28 | var cellPart = new CellPart(Universe, Cell); 29 | cellPart.Name = string.IsNullOrEmpty(Name)?"Unnamed cell":Name; 30 | this.Cell.Add(cellPart); 31 | 32 | } 33 | 34 | private void OnTicEvent(object o, WorldTicEventArgs e) => this.Tic(e.FpsTicNum); 35 | 36 | public Cell Cell {get; private set;} 37 | 38 | public double CurrentX => Cell.X ?? (Universe.World.X/2); 39 | public double CurrentY => Cell.Y ?? (Universe.World.Y/2); 40 | 41 | /* --- */ 42 | public IJSRuntime JsRuntime {get; private set;} 43 | public long VisibleAreaX { set; get; } 44 | public long VisibleAreaY { set; get; } 45 | 46 | public float Zoom { set; get; } = 8; 47 | 48 | private float DeltaZoom = 0; 49 | 50 | /* --- */ 51 | public long XGame2Physics( double x ) 52 | { 53 | var distance_to_cell = ( x - CurrentX ) * this.Zoom; 54 | var center_point = this.VisibleAreaX / 2; 55 | var distance_to_center_point = center_point + distance_to_cell; 56 | return ElementsHelper.TryConvert( distance_to_center_point ); 57 | } 58 | public long YGame2Physics( double y ) 59 | { 60 | var distance_to_cell = ( y - CurrentY ) * this.Zoom; 61 | var center_point = this.VisibleAreaY / 2; 62 | var distance_to_center_point = center_point + distance_to_cell; 63 | return ElementsHelper.TryConvert( distance_to_center_point ); 64 | } 65 | 66 | /* --- */ 67 | public long XGame2World( double x ) 68 | { 69 | return ElementsHelper.TryConvert( x * this.Zoom); 70 | } 71 | public long YGame2World( double y ) 72 | { 73 | return ElementsHelper.TryConvert( y * this.Zoom); 74 | } 75 | 76 | internal void IncreaseZoom(float v) 77 | { 78 | DeltaZoom += v; 79 | } 80 | 81 | internal void PointTo(double bx, double by) 82 | { 83 | Cell.PointTo(bx, by); 84 | } 85 | 86 | public void Tic(int fpsTicNum) 87 | { 88 | this.Cell.Purge(); 89 | 90 | if (DeltaZoom<0 && Zoom<0.5) 91 | { 92 | DeltaZoom=0; 93 | } 94 | 95 | if (DeltaZoom>0 && Zoom>9) 96 | { 97 | DeltaZoom=0; 98 | } 99 | 100 | if (DeltaZoom != 0) 101 | { 102 | var d = DeltaZoom / 20; 103 | DeltaZoom -= d; 104 | Zoom += d; 105 | } 106 | 107 | if ( !Cell.IsDead && (fpsTicNum+8) % TimedHostedService.fps == 0) 108 | { 109 | Task.Run( async () => await SetFocusToUniverse() ); 110 | } 111 | } 112 | 113 | /* --- */ 114 | public double XPysics2Game( long x ) 115 | { 116 | var center_point = this.VisibleAreaX / 2; 117 | var distance_to_center_point = (x - center_point) / this.Zoom; 118 | var position = CurrentX + distance_to_center_point; 119 | return position; 120 | } 121 | 122 | public double YPysics2Game( long y ) 123 | { 124 | var center_point = this.VisibleAreaY / 2; 125 | var distance_to_center_point = (y - center_point) / this.Zoom; 126 | var position = CurrentY + distance_to_center_point; 127 | return position; 128 | } 129 | 130 | public bool OnArea(AgarElement e) 131 | { 132 | if (e==null) return false; 133 | if (e.ElementType == ElementType.Universe ) return true; 134 | if (e.ElementType == ElementType.World ) return true; 135 | var nTimesTheDiameter = Math.Max( ( Cell.Diameter ?? 50 ) * 5, 30); 136 | if (CurrentX - e.X + e.Radius > nTimesTheDiameter ) return false; 137 | if (e.X - e.Radius - CurrentX > nTimesTheDiameter ) return false; 138 | if (CurrentY - e.Y + e.Radius > nTimesTheDiameter ) return false; 139 | if (e.Y - e.Radius - CurrentY > nTimesTheDiameter ) return false; 140 | return true; 141 | } 142 | 143 | /* --- */ 144 | private System.Timers.Timer aTimer; 145 | 146 | [JSInvokable] 147 | public async Task OnBrowserResize() 148 | { 149 | if (aTimer == null) 150 | { 151 | await CheckVisibleArea(); 152 | aTimer = new System.Timers.Timer(500); 153 | aTimer.Elapsed += CheckVisibleAreaWrapper; 154 | aTimer.AutoReset = false; 155 | } 156 | else{ 157 | aTimer.Stop(); 158 | aTimer.Start(); 159 | } 160 | } 161 | 162 | private void CheckVisibleAreaWrapper(Object source, ElapsedEventArgs e) 163 | { 164 | Task.Run( async() => await CheckVisibleArea() ); 165 | } 166 | 167 | public async Task CheckVisibleArea(IJSRuntime jsRuntime = null) 168 | { 169 | JsRuntime = jsRuntime ?? JsRuntime; 170 | var visibleArea = await JsRuntime.InvokeAsync("GetSize"); 171 | this.VisibleAreaX = visibleArea[0]; 172 | this.VisibleAreaY = visibleArea[1]; 173 | await JsRuntime.InvokeAsync("AreaResized", DotNetObjectReference.Create(this) ); 174 | } 175 | 176 | public async Task SetFocusToUniverse(IJSRuntime jsRuntime = null) 177 | { 178 | JsRuntime = jsRuntime ?? JsRuntime; 179 | await JsRuntime.InvokeAsync("SetFocusToUniverse"); 180 | } 181 | 182 | public void Dispose() 183 | { 184 | Universe.World.OnTicReached -= OnTicEvent; 185 | } 186 | 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | tmp/* 33 | 34 | # Visual Studio 2017 auto generated files 35 | Generated\ Files/ 36 | 37 | # MSTest test Results 38 | [Tt]est[Rr]esult*/ 39 | [Bb]uild[Ll]og.* 40 | 41 | # NUNIT 42 | *.VisualState.xml 43 | TestResult.xml 44 | 45 | # Build Results of an ATL Project 46 | [Dd]ebugPS/ 47 | [Rr]eleasePS/ 48 | dlldata.c 49 | 50 | # Benchmark Results 51 | BenchmarkDotNet.Artifacts/ 52 | 53 | # .NET Core 54 | project.lock.json 55 | project.fragment.lock.json 56 | artifacts/ 57 | **/Properties/launchSettings.json 58 | 59 | # StyleCop 60 | StyleCopReport.xml 61 | 62 | # Files built by Visual Studio 63 | *_i.c 64 | *_p.c 65 | *_i.h 66 | *.ilk 67 | *.meta 68 | *.obj 69 | *.iobj 70 | *.pch 71 | *.pdb 72 | *.ipdb 73 | *.pgc 74 | *.pgd 75 | *.rsp 76 | *.sbr 77 | *.tlb 78 | *.tli 79 | *.tlh 80 | *.tmp 81 | *.tmp_proj 82 | *.log 83 | *.vspscc 84 | *.vssscc 85 | .builds 86 | *.pidb 87 | *.svclog 88 | *.scc 89 | 90 | # Chutzpah Test files 91 | _Chutzpah* 92 | 93 | # Visual C++ cache files 94 | ipch/ 95 | *.aps 96 | *.ncb 97 | *.opendb 98 | *.opensdf 99 | *.sdf 100 | *.cachefile 101 | *.VC.db 102 | *.VC.VC.opendb 103 | 104 | # Visual Studio profiler 105 | *.psess 106 | *.vsp 107 | *.vspx 108 | *.sap 109 | 110 | # Visual Studio Trace Files 111 | *.e2e 112 | 113 | # TFS 2012 Local Workspace 114 | $tf/ 115 | 116 | # Guidance Automation Toolkit 117 | *.gpState 118 | 119 | # ReSharper is a .NET coding add-in 120 | _ReSharper*/ 121 | *.[Rr]e[Ss]harper 122 | *.DotSettings.user 123 | 124 | # JustCode is a .NET coding add-in 125 | .JustCode 126 | 127 | # TeamCity is a build add-in 128 | _TeamCity* 129 | 130 | # DotCover is a Code Coverage Tool 131 | *.dotCover 132 | 133 | # AxoCover is a Code Coverage Tool 134 | .axoCover/* 135 | !.axoCover/settings.json 136 | 137 | # Visual Studio code coverage results 138 | *.coverage 139 | *.coveragexml 140 | 141 | # NCrunch 142 | _NCrunch_* 143 | .*crunch*.local.xml 144 | nCrunchTemp_* 145 | 146 | # MightyMoose 147 | *.mm.* 148 | AutoTest.Net/ 149 | 150 | # Web workbench (sass) 151 | .sass-cache/ 152 | 153 | # Installshield output folder 154 | [Ee]xpress/ 155 | 156 | # DocProject is a documentation generator add-in 157 | DocProject/buildhelp/ 158 | DocProject/Help/*.HxT 159 | DocProject/Help/*.HxC 160 | DocProject/Help/*.hhc 161 | DocProject/Help/*.hhk 162 | DocProject/Help/*.hhp 163 | DocProject/Help/Html2 164 | DocProject/Help/html 165 | 166 | # Click-Once directory 167 | publish/ 168 | 169 | # Publish Web Output 170 | *.[Pp]ublish.xml 171 | *.azurePubxml 172 | # Note: Comment the next line if you want to checkin your web deploy settings, 173 | # but database connection strings (with potential passwords) will be unencrypted 174 | *.pubxml 175 | *.publishproj 176 | 177 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 178 | # checkin your Azure Web App publish settings, but sensitive information contained 179 | # in these scripts will be unencrypted 180 | PublishScripts/ 181 | 182 | # NuGet Packages 183 | *.nupkg 184 | # The packages folder can be ignored because of Package Restore 185 | **/[Pp]ackages/* 186 | # except build/, which is used as an MSBuild target. 187 | !**/[Pp]ackages/build/ 188 | # Uncomment if necessary however generally it will be regenerated when needed 189 | #!**/[Pp]ackages/repositories.config 190 | # NuGet v3's project.json files produces more ignorable files 191 | *.nuget.props 192 | *.nuget.targets 193 | 194 | # Microsoft Azure Build Output 195 | csx/ 196 | *.build.csdef 197 | 198 | # Microsoft Azure Emulator 199 | ecf/ 200 | rcf/ 201 | 202 | # Windows Store app package directories and files 203 | AppPackages/ 204 | BundleArtifacts/ 205 | Package.StoreAssociation.xml 206 | _pkginfo.txt 207 | *.appx 208 | 209 | # Visual Studio cache files 210 | # files ending in .cache can be ignored 211 | *.[Cc]ache 212 | # but keep track of directories ending in .cache 213 | !*.[Cc]ache/ 214 | 215 | # Others 216 | ClientBin/ 217 | ~$* 218 | *~ 219 | *.dbmdl 220 | *.dbproj.schemaview 221 | *.jfm 222 | *.pfx 223 | *.publishsettings 224 | orleans.codegen.cs 225 | 226 | # Including strong name files can present a security risk 227 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 228 | #*.snk 229 | 230 | # Since there are multiple workflows, uncomment next line to ignore bower_components 231 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 232 | #bower_components/ 233 | 234 | # RIA/Silverlight projects 235 | Generated_Code/ 236 | 237 | # Backup & report files from converting an old project file 238 | # to a newer Visual Studio version. Backup files are not needed, 239 | # because we have git ;-) 240 | _UpgradeReport_Files/ 241 | Backup*/ 242 | UpgradeLog*.XML 243 | UpgradeLog*.htm 244 | ServiceFabricBackup/ 245 | *.rptproj.bak 246 | 247 | # SQL Server files 248 | *.mdf 249 | *.ldf 250 | *.ndf 251 | 252 | # Business Intelligence projects 253 | *.rdl.data 254 | *.bim.layout 255 | *.bim_*.settings 256 | *.rptproj.rsuser 257 | 258 | # Microsoft Fakes 259 | FakesAssemblies/ 260 | 261 | # GhostDoc plugin setting file 262 | *.GhostDoc.xml 263 | 264 | # Node.js Tools for Visual Studio 265 | .ntvs_analysis.dat 266 | node_modules/ 267 | 268 | # Visual Studio 6 build log 269 | *.plg 270 | 271 | # Visual Studio 6 workspace options file 272 | *.opt 273 | 274 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 275 | *.vbw 276 | 277 | # Visual Studio LightSwitch build output 278 | **/*.HTMLClient/GeneratedArtifacts 279 | **/*.DesktopClient/GeneratedArtifacts 280 | **/*.DesktopClient/ModelManifest.xml 281 | **/*.Server/GeneratedArtifacts 282 | **/*.Server/ModelManifest.xml 283 | _Pvt_Extensions 284 | 285 | # Paket dependency manager 286 | .paket/paket.exe 287 | paket-files/ 288 | 289 | # FAKE - F# Make 290 | .fake/ 291 | 292 | # JetBrains Rider 293 | .idea/ 294 | *.sln.iml 295 | 296 | # CodeRush 297 | .cr/ 298 | 299 | # Python Tools for Visual Studio (PTVS) 300 | __pycache__/ 301 | *.pyc 302 | 303 | # Cake - Uncomment if you are using it 304 | # tools/** 305 | # !tools/packages.config 306 | 307 | # Tabs Studio 308 | *.tss 309 | 310 | # Telerik's JustMock configuration file 311 | *.jmconfig 312 | 313 | # BizTalk build output 314 | *.btp.cs 315 | *.btm.cs 316 | *.odx.cs 317 | *.xsd.cs 318 | 319 | # OpenCover UI analysis results 320 | OpenCover/ 321 | 322 | # Azure Stream Analytics local run output 323 | ASALocalRun/ 324 | 325 | # MSBuild Binary and Structured Log 326 | *.binlog 327 | 328 | # NVidia Nsight GPU debugger configuration file 329 | *.nvuser 330 | 331 | # MFractors (Xamarin productivity tool) working folder 332 | .mfractor/ 333 | -------------------------------------------------------------------------------- /src/blagario/AgarElements/World.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace blagario.elements 7 | { 8 | 9 | public class WorldTicEventArgs: EventArgs 10 | { 11 | public int FpsTicNum {set;get;} 12 | } 13 | 14 | public class World : AgarElement 15 | { 16 | public List Elements; 17 | public const double WorldSize = 1000; 18 | public override long MaxMass {set; get; } = 60 * 1000; 19 | public const long MaxViruses = 100; 20 | public const long MaxPellets = 1000; 21 | 22 | public override string CssStyle( Player c ) =>$@" 23 | top: {c.YGame2Physics(0)}px ; 24 | left: {c.XGame2Physics(0)}px; 25 | width: {(ElementsHelper.TryConvert(X * c.Zoom)).ToString()}px ; 26 | height: {(ElementsHelper.TryConvert(Y * c.Zoom)).ToString()}px ; 27 | "; 28 | 29 | public World(Universe universe) 30 | { 31 | this.X = WorldSize; 32 | this.Y = WorldSize; 33 | Elements = new List(); 34 | Universe = universe; 35 | } 36 | 37 | public long TotalMass => Elements.Sum( x=> x.Mass ); 38 | public IEnumerable Viruses => Elements.Where(x => x.ElementType == ElementType.Virus ).Select(x=>x as Virus); 39 | public IEnumerable Pellets => Elements.Where(x => x.ElementType == ElementType.Pellet ).Select(x=>x as Pellet); 40 | 41 | public IEnumerable Leaderboard => Elements.Where(x => x.ElementType == ElementType.CellPart ).Select(x=> x as CellPart).OrderByDescending(x => x._Mass ).Take(10); 42 | 43 | 44 | public event EventHandler OnTicReached; 45 | 46 | protected virtual void OnTic(WorldTicEventArgs e) 47 | { 48 | var handler = OnTicReached; 49 | handler?.Invoke(this, e); 50 | } 51 | public override async Task Tic(int fpsTicNum) 52 | { 53 | List currentElements; 54 | 55 | lock(this.Elements) currentElements = this.Elements.OrderBy(e=>e._Mass).ToList(); // primer pintem els petits després els grans. 56 | foreach (var e in currentElements) await e.Tic(fpsTicNum); 57 | 58 | if (fpsTicNum % (TimedHostedService.fps/3) == 0) // 3 times per second 59 | { 60 | ManageCollitions(currentElements); 61 | } 62 | 63 | if ( (fpsTicNum+5) % TimedHostedService.fps == 0) // 1 time per second 64 | { 65 | currentElements = FillWorld(ElementType.Virus); 66 | } 67 | 68 | if ( (fpsTicNum+25) % TimedHostedService.fps == 0) // 1 time per second 69 | { 70 | currentElements = FillWorld(ElementType.Pellet); 71 | } 72 | 73 | await base.Tic(fpsTicNum); 74 | 75 | OnTic( new WorldTicEventArgs {FpsTicNum=fpsTicNum} ); 76 | } 77 | 78 | private List FillWorld(ElementType elementType) 79 | { 80 | List currentElements; 81 | lock (this.Elements) currentElements = this.Elements.ToList(); 82 | if (elementType == ElementType.Virus) CheckIfWoldNedsMoreViruses(currentElements); 83 | if (elementType == ElementType.Pellet) CheckIfWoldNedsMorePellets(currentElements); 84 | return currentElements; 85 | } 86 | 87 | private void ManageCollitions(List currentElements) 88 | { 89 | var collisions = LocateCollisions(currentElements); 90 | ResolveCollitions(collisions); 91 | lock(this.Elements) this.Elements.OrderBy(x=>x._Mass).ToList().RemoveAll(e=>e._Mass == 0); 92 | } 93 | 94 | private List<( AgarElement eater, List eateds)> LocateCollisions(List currentElements) 95 | { 96 | List<( AgarElement eater, List eateds)> collision = new List<( AgarElement eater, List eateds)>(); 97 | 98 | var cells = 99 | currentElements 100 | .Select( (e,i) => new {e,i} ) 101 | .Where(x=>x.e.ElementType == ElementType.CellPart) 102 | .ToList(); 103 | 104 | foreach( var currentCell in cells ) 105 | { 106 | var p = currentElements 107 | .Take( currentCell.i ) 108 | .Where( otherElement => 109 | ElementsHelper.CanOneElementEatsOtherOneByMass( currentCell.e, otherElement ) && 110 | ElementsHelper.CanOneElementEatsOtherOneByCellGroup(currentCell.e, otherElement) && 111 | ElementsHelper.CanOneElementEatsOtherOneByDistance( currentCell.e, otherElement ) ) 112 | .ToList(); 113 | 114 | if (p.Any()) 115 | { 116 | collision.Add( (currentCell.e, p) ); 117 | } 118 | } 119 | return collision; 120 | } 121 | 122 | private void ResolveCollitions(List<( AgarElement eater, List eateds)> collisions) 123 | { 124 | foreach(var (eater, eateds) in collisions) 125 | foreach(var eated in eateds) 126 | { 127 | if (eated._Mass == 0) continue; 128 | var t = (eater.ElementType, eated.ElementType ); 129 | var _ = 130 | t == (ElementType.CellPart, ElementType.Pellet)?ResolveEatElements( eater as CellPart, eated as Pellet): 131 | t == (ElementType.CellPart, ElementType.CellPart)?ResolveEatElements( eater as CellPart, eated as CellPart): 132 | t == (ElementType.CellPart, ElementType.Virus)?ResolveEatElements( eater as CellPart, eated as Virus): 133 | 0; 134 | } 135 | } 136 | 137 | private int ResolveEatElements(CellPart eater, Pellet eated) 138 | { 139 | eater._EatedMass += eated._Mass; 140 | eated._Mass = 0; 141 | return 1; 142 | } 143 | private int ResolveEatElements(CellPart eater, CellPart eated) 144 | { 145 | 146 | eater._EatedMass += eated._Mass; 147 | eated._Mass = 0; 148 | return 1; 149 | } 150 | private int ResolveEatElements(CellPart eater, Virus eated) 151 | { 152 | eater._EatedMass += eated._Mass; 153 | eated._Mass = 0; 154 | return 1; 155 | } 156 | 157 | 158 | private void CheckIfWoldNedsMorePellets(List currentElements) 159 | { 160 | var nPellets = currentElements.Where(x=>x.ElementType == ElementType.Pellet).Count(); 161 | var mass = currentElements.Sum(e=>e.Mass); 162 | var worldNeedsMorePellets = nPellets < MaxPellets && mass < MaxMass; 163 | if (worldNeedsMorePellets) lock(this.Elements) while( nPellets < MaxPellets && mass < MaxMass ) 164 | { 165 | var e = Pellet.CreatePellet(this.Universe); 166 | mass += e.Mass; 167 | nPellets++; 168 | } 169 | } 170 | 171 | private void CheckIfWoldNedsMoreViruses(List currentElements) 172 | { 173 | var nViruses = currentElements.Where(x=>x.ElementType == ElementType.Virus).Count(); 174 | var mass = currentElements.Sum(e=>e.Mass); 175 | var worldNeedsMoreViruses = (nViruses < MaxViruses && mass < MaxMass); 176 | if (worldNeedsMoreViruses) lock(this.Elements) while( nViruses < MaxViruses && mass < MaxMass ) 177 | { 178 | var e = Virus.CreateVirus(this.Universe); 179 | mass += e.Mass; 180 | nViruses++; 181 | } 182 | } 183 | } 184 | } 185 | --------------------------------------------------------------------------------