mirror of
https://github.com/OpenLiberty/liberty-bikes.git
synced 2025-01-30 10:40:13 +08:00
Merge pull request #64 from aguibert/leaderboard
Initial draft of leaderboard
This commit is contained in:
commit
7ef54ea77d
@ -73,6 +73,10 @@ task copyFrontend(type: Copy) {
|
||||
into "${rootDir}/frontend/src/main/webapp"
|
||||
}
|
||||
|
||||
npm_start {
|
||||
dependsOn 'libertyStop'
|
||||
}
|
||||
|
||||
liberty {
|
||||
server {
|
||||
name = 'frontendServer'
|
||||
|
@ -12,6 +12,7 @@ import { GameComponent } from './game/game.component';
|
||||
import { ControlsComponent } from './controls/controls.component';
|
||||
import { PlayerListComponent } from './game/playerlist/playerlist.component';
|
||||
import { PlayerComponent } from './game/player/player.component';
|
||||
import { LeaderboardComponent } from './game/leaderboard/leaderboard.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -27,6 +28,7 @@ import { PlayerComponent } from './game/player/player.component';
|
||||
ControlsComponent,
|
||||
PlayerListComponent,
|
||||
PlayerComponent,
|
||||
LeaderboardComponent,
|
||||
],
|
||||
providers: [ ],
|
||||
bootstrap: [ AppComponent ]
|
||||
|
@ -19,7 +19,7 @@
|
||||
</div>
|
||||
|
||||
<div id="leaderboard">
|
||||
|
||||
<rank-list></rank-list>
|
||||
</div>
|
||||
|
||||
<div id="footer" class="navbar">
|
||||
|
@ -0,0 +1,23 @@
|
||||
<div id="title"><h2>Leaderboard</h2></div>
|
||||
<table class="leaderboard-container">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>Rank</td>
|
||||
<td>Player Name</td>
|
||||
<td>Wins</td>
|
||||
<td>Games Played</td>
|
||||
<td>Win rate</td>
|
||||
<td>Rating</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let ranking of rankings">
|
||||
<td>{{ ranking.rank }}</td>
|
||||
<td>{{ ranking.name }}</td>
|
||||
<td>{{ ranking.numWins }}</td>
|
||||
<td>{{ ranking.totalGames }}</td>
|
||||
<td>{{ ranking.winLossRatio }} %</td>
|
||||
<td>{{ ranking.rating }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
@ -0,0 +1,34 @@
|
||||
:host {
|
||||
display: grid;
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
grid-template-columns: 50px 1fr;
|
||||
min-height: 100%;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
#title {
|
||||
grid-area: 1 / 1 / span 2 / 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#title h2 {
|
||||
transform: rotate(-90deg);
|
||||
color: white;
|
||||
|
||||
text-transform: lowercase;
|
||||
font-variant: small-caps;
|
||||
|
||||
letter-spacing: .1em;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.leaderboard-container {
|
||||
margin-left: 15px;
|
||||
margin-top: 15px;
|
||||
margin-right: 15px;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LeaderboardComponent } from './leaderboard.component';
|
||||
|
||||
describe('LeaderboardComponent', () => {
|
||||
let component: LeaderboardComponent;
|
||||
let fixture: ComponentFixture<LeaderboardComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LeaderboardComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LeaderboardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,42 @@
|
||||
import { Component, OnInit, NgZone } from '@angular/core';
|
||||
import { Ranking } from './ranking/ranking';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { environment } from './../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'rank-list',
|
||||
templateUrl: './leaderboard.component.html',
|
||||
styleUrls: ['./leaderboard.component.scss']
|
||||
})
|
||||
export class LeaderboardComponent implements OnInit {
|
||||
rankings: Ranking[] = new Array();
|
||||
|
||||
constructor(private ngZone: NgZone, private http: HttpClient) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.getLeaders();
|
||||
}
|
||||
|
||||
async getLeaders() {
|
||||
try {
|
||||
console.log("Calling rank service");
|
||||
let data = await this.http.get(`${environment.API_URL_RANKS}/top/10`).toPromise();
|
||||
console.log(`Got leaders: ${JSON.stringify(data)}`);
|
||||
const json = data as any;
|
||||
const rankingsArr = new Array();
|
||||
let i = 1;
|
||||
for (let ranking of json) {
|
||||
console.log(`Got rank ${JSON.stringify(ranking)}`);
|
||||
rankingsArr.push(new Ranking(i++, ranking.name, ranking.stats.numWins, ranking.stats.totalGames, ranking.stats.rating));
|
||||
}
|
||||
this.ngZone.run(() => {
|
||||
this.rankings = rankingsArr;
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
export class Ranking {
|
||||
public rank: number;
|
||||
public name: string;
|
||||
public numWins: number;
|
||||
public winLossRatio: string;
|
||||
public totalGames: number;
|
||||
public rating: number;
|
||||
|
||||
constructor(rank: number, name: string, numWins: number, totalGames: number, rating: number) {
|
||||
this.rank = rank;
|
||||
this.name = name;
|
||||
this.numWins = numWins;
|
||||
this.totalGames = totalGames;
|
||||
this.winLossRatio = totalGames === 0 ? '--' : Number((numWins / totalGames) * 100).toFixed();
|
||||
this.rating = rating;
|
||||
}
|
||||
}
|
@ -79,7 +79,6 @@ public class GameBoard {
|
||||
preferredPlayerSlots.put(playerId, playerNum);
|
||||
}
|
||||
takenPlayerSlots[playerNum] = true;
|
||||
System.out.println("Player slot " + playerNum + " taken");
|
||||
|
||||
// Don't let the preferred player slot map take up too much memory
|
||||
if (preferredPlayerSlots.size() > 1000)
|
||||
|
@ -4,7 +4,9 @@ import static org.libertybikes.game.round.service.GameRoundWebsocket.sendTextToC
|
||||
import static org.libertybikes.game.round.service.GameRoundWebsocket.sendTextToClients;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Date;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
@ -60,6 +62,7 @@ public class GameRound implements Runnable {
|
||||
private final AtomicBoolean paused = new AtomicBoolean();
|
||||
private final AtomicBoolean heartbeatStarted = new AtomicBoolean();
|
||||
private final Map<Session, Client> clients = new HashMap<>();
|
||||
private final Deque<Player> playerRanks = new ArrayDeque<>();
|
||||
|
||||
private int ticksFromGameEnd = 0;
|
||||
|
||||
@ -250,14 +253,12 @@ public class GameRound implements Runnable {
|
||||
return; // Don't update player stats for single-player games
|
||||
|
||||
PlayerService playerSvc = CDI.current().select(PlayerService.class, RestClient.LITERAL).get();
|
||||
for (Player p : players) {
|
||||
if (p.getStatus() == STATUS.Winner) {
|
||||
log("Player " + p.name + " has won the round");
|
||||
playerSvc.addWin(p.id);
|
||||
} else {
|
||||
log("Player " + p.name + " has participated in the round");
|
||||
playerSvc.addLoss(p.id);
|
||||
}
|
||||
int rank = 1;
|
||||
for (Player p : playerRanks) {
|
||||
log("Player " + p.name + " came in place " + rank);
|
||||
if (p.isRealPlayer())
|
||||
playerSvc.recordGame(p.id, rank);
|
||||
rank++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,6 +282,7 @@ public class GameRound implements Runnable {
|
||||
playersMoved = true;
|
||||
} else {
|
||||
death = true;
|
||||
playerRanks.push(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -337,6 +339,7 @@ public class GameRound implements Runnable {
|
||||
}
|
||||
if (alivePlayers == 1) {
|
||||
alive.setStatus(STATUS.Winner);
|
||||
playerRanks.push(alive);
|
||||
gameState = State.FINISHED;
|
||||
}
|
||||
|
||||
|
@ -262,8 +262,13 @@ public class Player {
|
||||
return isAlive;
|
||||
}
|
||||
|
||||
@JsonbTransient
|
||||
public boolean isRealPlayer() {
|
||||
return ai == null;
|
||||
}
|
||||
|
||||
public void processAIMove(short[][] board) {
|
||||
if (ai == null)
|
||||
if (isRealPlayer())
|
||||
return;
|
||||
try {
|
||||
direction = ai.processGameTick(board);
|
||||
|
@ -6,8 +6,8 @@ import javax.ws.rs.POST;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
|
||||
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
|
||||
|
||||
@ -22,11 +22,7 @@ public interface PlayerService {
|
||||
public Player getPlayerById(@PathParam("playerId") String id);
|
||||
|
||||
@POST
|
||||
@Path("/{playerId}/win")
|
||||
public Response addWin(@PathParam("playerId") String id);
|
||||
|
||||
@POST
|
||||
@Path("/{playerId}/loss")
|
||||
public Response addLoss(@PathParam("playerId") String id);
|
||||
@Path("/{playerId}/recordGame")
|
||||
public void recordGame(@PathParam("playerId") String id, @QueryParam("place") int place);
|
||||
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ public class PlayerDB {
|
||||
public Collection<Player> topPlayers(int numPlayers) {
|
||||
return allPlayers.values()
|
||||
.stream()
|
||||
.sorted(Player::compareByWins)
|
||||
.sorted(Player::compareOverall)
|
||||
.limit(numPlayers)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
@ -2,10 +2,14 @@ package org.libertybikes.player.service;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.json.bind.Jsonb;
|
||||
import javax.json.bind.JsonbBuilder;
|
||||
import javax.json.bind.annotation.JsonbCreator;
|
||||
|
||||
public class Player {
|
||||
|
||||
private static final Jsonb jsonb = JsonbBuilder.create();
|
||||
|
||||
public final String id;
|
||||
|
||||
public final String name;
|
||||
@ -23,11 +27,30 @@ public class Player {
|
||||
}
|
||||
|
||||
public static int compareByWins(Player a, Player b) {
|
||||
return b.stats.numWins - a.stats.numWins;
|
||||
return Integer.compare(b.stats.numWins, a.stats.numWins);
|
||||
}
|
||||
|
||||
public static double compareByWinRatio(Player a, Player b) {
|
||||
return b.stats.winLossRatio() - a.stats.winLossRatio();
|
||||
public static int compareByWinRatio(Player a, Player b) {
|
||||
return Double.compare(b.stats.winLossRatio(), a.stats.winLossRatio());
|
||||
}
|
||||
|
||||
public static int compareByRating(Player a, Player b) {
|
||||
return Integer.compare(b.stats.rating, a.stats.rating);
|
||||
}
|
||||
|
||||
public static int compareOverall(Player a, Player b) {
|
||||
int rating = compareByRating(a, b);
|
||||
if (rating != 0)
|
||||
return rating;
|
||||
int wins = compareByWins(a, b);
|
||||
if (wins != 0)
|
||||
return wins;
|
||||
return compareByWinRatio(a, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return jsonb.toJson(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -29,12 +29,8 @@ public class PlayerService {
|
||||
Random r = new Random();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
String id = createPlayer("SamplePlayer-" + i);
|
||||
int wins = r.nextInt(3);
|
||||
int losses = r.nextInt(3);
|
||||
for (int w = 0; w < wins; w++)
|
||||
addWin(id);
|
||||
for (int l = 0; l < losses; l++)
|
||||
addLoss(id);
|
||||
for (int j = 0; j < 3; j++)
|
||||
recordGame(id, r.nextInt(4) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,26 +60,27 @@ public class PlayerService {
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{playerId}/win")
|
||||
public void addWin(@PathParam("playerId") String id) {
|
||||
@Path("/{playerId}/recordGame")
|
||||
public void recordGame(@PathParam("playerId") String id, @QueryParam("place") int place) {
|
||||
Player p = getPlayerById(id);
|
||||
if (p == null)
|
||||
return;
|
||||
p.stats.totalGames++;
|
||||
switch (place) {
|
||||
case 1:
|
||||
p.stats.numWins++;
|
||||
p.stats.totalGames++;
|
||||
p.stats.rating += 28;
|
||||
break;
|
||||
case 2:
|
||||
p.stats.rating += 14;
|
||||
break;
|
||||
case 3:
|
||||
p.stats.rating -= 5;
|
||||
break;
|
||||
default:
|
||||
p.stats.rating -= 12;
|
||||
}
|
||||
db.put(p);
|
||||
System.out.println("Player " + id + " has won " + p.stats.numWins + " games and played in " + p.stats.totalGames + " games.");
|
||||
System.out.println(p);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/{playerId}/loss")
|
||||
public void addLoss(@PathParam("playerId") String id) {
|
||||
Player p = getPlayerById(id);
|
||||
if (p == null)
|
||||
return;
|
||||
p.stats.totalGames++;
|
||||
db.put(p);
|
||||
System.out.println("Player " + id + " has won " + p.stats.numWins + " games and played in " + p.stats.totalGames + " games.");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,8 +6,10 @@ public class PlayerStats {
|
||||
|
||||
public int numWins;
|
||||
|
||||
public int rating = 1000;
|
||||
|
||||
public double winLossRatio() {
|
||||
return numWins / totalGames;
|
||||
return totalGames == 0 ? 0 : numWins / totalGames;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.libertybikes.player.service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.enterprise.context.ApplicationScoped;
|
||||
import javax.inject.Inject;
|
||||
@ -20,9 +21,21 @@ public class RankingService {
|
||||
PlayerDB db;
|
||||
|
||||
@GET
|
||||
@Path("/top")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Collection<Player> topPlayers() {
|
||||
return db.topPlayers(5);
|
||||
public Collection<Player> topFivePlayers() {
|
||||
return topNPlayers(5);
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("/top/{numPlayers}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
public Collection<Player> topNPlayers(@PathParam("numPlayers") Integer numPlayers) {
|
||||
if (numPlayers < 0)
|
||||
return Collections.emptySet();
|
||||
if (numPlayers > 100)
|
||||
numPlayers = 100;
|
||||
return db.topPlayers(numPlayers);
|
||||
}
|
||||
|
||||
@GET
|
||||
|
@ -2,6 +2,7 @@
|
||||
<featureManager>
|
||||
<feature>microProfile-1.2</feature>
|
||||
<feature>jaxrs-2.1</feature>
|
||||
<feature>jsonb-1.0</feature>
|
||||
</featureManager>
|
||||
|
||||
<httpEndpoint id="defaultHttpEndpoint" host="*" httpPort="${httpPort}" httpsPort="${httpsPort}" />
|
||||
|
Loading…
Reference in New Issue
Block a user