initial commit
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
<nb-flip-card [flipped]="cardFlipped" [showToggleButton]="false">
|
||||
<nb-card-front>
|
||||
<nb-card [status]="isHost?'info':'primary'">
|
||||
<nb-card-header>
|
||||
{{title}}
|
||||
<button nbButton hero status="success" size="small" class="float-right"
|
||||
(click)="cardFlipped=!cardFlipped">看看</button>
|
||||
</nb-card-header>
|
||||
<nb-card-body *ngIf="!isLoading">
|
||||
|
||||
<div [ngSwitch]="stage">
|
||||
<avalon-join-game *ngSwitchCase="AvalonStage.JoinGame"></avalon-join-game>
|
||||
<avalon-choose-character *ngSwitchCase="AvalonStage.NewGame"></avalon-choose-character>
|
||||
<avalon-pick-teammate *ngSwitchCase="AvalonStage.PickTeammate"></avalon-pick-teammate>
|
||||
<avalon-team-vote *ngSwitchCase="AvalonStage.TeamVote"></avalon-team-vote>
|
||||
<avalon-vote-result *ngSwitchCase="AvalonStage.TeamVoteResult" [isTeamVote]="true">
|
||||
</avalon-vote-result>
|
||||
<avalon-quest-vote *ngSwitchCase="AvalonStage.QuestVote"></avalon-quest-vote>
|
||||
<avalon-vote-result *ngSwitchCase="AvalonStage.QuestVoteResult" [isTeamVote]="false">
|
||||
</avalon-vote-result>
|
||||
<div *ngSwitchDefault [ngTemplateOutlet]="WaitingMessage"></div>
|
||||
|
||||
</div>
|
||||
<ng-template #WaitingMessage>
|
||||
<h1>等待遊戲開始中...</h1>
|
||||
</ng-template>
|
||||
</nb-card-body>
|
||||
<nb-card-footer>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
|
||||
<button nbButton hero [ngClass]="{'d-none': !hasRole}" status="primary" size="small"
|
||||
class="mr-3" (click)="showMySecret()">發動特殊技能</button>
|
||||
<!-- <button nbButton hero status="primary" size="small" class="mr-3" (click)="testDlg()">Test</button> -->
|
||||
|
||||
<button nbButton hero [ngClass]="{'d-none': !hasRole}" status="primary" size="small"
|
||||
(click)="showMyRole()">看看我的腳色</button>
|
||||
</div>
|
||||
<div class="col-md-6 mt-3" *ngIf="isHost">
|
||||
|
||||
<button class="float-right mr-2" nbButton hero status="warning" size="small"
|
||||
(click)="previousStage()">上一部</button>
|
||||
<button class="float-right mr-2" nbButton hero status="primary" size="small"
|
||||
(click)="nextStage()">下一部</button>
|
||||
<button class="float-right mr-2" nbButton hero status="primary" size="small"
|
||||
(click)="broadcastAll()">Refresh</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- <button class="float-right" nbButton hero status="danger" size="small" (click)="close()"
|
||||
[nbSpinner]="processing">Close</button>
|
||||
<button class="float-right mr-2" nbButton hero status="primary" size="small" (click)="update()"
|
||||
[nbSpinner]="processing" [disabled]="!allowSubmit">Submit</button> -->
|
||||
</nb-card-footer>
|
||||
</nb-card>
|
||||
</nb-card-front>
|
||||
<nb-card-back>
|
||||
<nb-card>
|
||||
<nb-card-header>
|
||||
戰況總攬
|
||||
<button nbButton hero status="success" size="small" class="float-right"
|
||||
(click)="cardFlipped=!cardFlipped">看看</button>
|
||||
</nb-card-header>
|
||||
<nb-card-body>
|
||||
<avalon-quest-table></avalon-quest-table>
|
||||
</nb-card-body>
|
||||
</nb-card>
|
||||
</nb-card-back>
|
||||
</nb-flip-card>
|
||||
|
||||
<nb-card *ngIf="!isLoading&&(isHost||stage>1)">
|
||||
<nb-card-body [innerHtml]="playerList"></nb-card-body>
|
||||
</nb-card>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AvalonComponent } from './avalon.component';
|
||||
|
||||
describe('AvalonComponent', () => {
|
||||
let component: AvalonComponent;
|
||||
let fixture: ComponentFixture<AvalonComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ AvalonComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AvalonComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,386 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { first, takeUntil } from 'rxjs/operators';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import { AvalonStage, AVALON_EVIL_KNOWN_TO_EVIL, AVALON_EVIL_KNOWN_TO_MERLIN, AVALON_KNOWN_TO_PERCIVAL, GameInfo, Player, PlayerAction, QuestInfo, Role, RoleInfo } from '../../entity/Avalon';
|
||||
import { AvalonService } from '../../services/avalon.service';
|
||||
import * as signalR from "@microsoft/signalr"
|
||||
import { StringUtils } from '../../utilities/string-utils';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { MsgBoxService } from '../../services/msg-box.service';
|
||||
import { ADButtons, ADIcon } from '../../ui/alert-dlg/alert-dlg.component';
|
||||
import { UuidUtils } from '../../utilities/uuid-utils';
|
||||
import { ObjectUtils } from '../../utilities/object-utils';
|
||||
import { NbToastrService } from '@nebular/theme';
|
||||
import { AvalonBase } from './avalonBase';
|
||||
import { HeaderService } from '../../services/header.service';
|
||||
|
||||
const minimumPlayers = 5;
|
||||
const maximumPlayers = 10;
|
||||
const players_good: number[] = [3, 4, 4, 5, 6, 6];
|
||||
const players_evil: number[] = [2, 2, 3, 3, 3, 4];
|
||||
const teamSize: number[][] = [
|
||||
[2, 3, 2, 3, 3],
|
||||
[2, 3, 4, 3, 4],
|
||||
[2, 3, 3, 4, 4],
|
||||
[3, 4, 4, 5, 5],
|
||||
[3, 4, 4, 5, 5],
|
||||
[3, 4, 4, 5, 5]
|
||||
];
|
||||
const fourthQuestNeed2Failed = 7;
|
||||
const SIGNAL_R_URL = (id: string = null) => { return `${environment.signalRUrl}/${id}Hub` }
|
||||
//const SIGNAL_R_URL = (id: string = null) => { return `http://localhost:12071/hub` }
|
||||
//const SIGNAL_R_URL = (id: string = null) => { return `http://happiness.tours:8088/${id}hub` }
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-avalon',
|
||||
templateUrl: './avalon.component.html',
|
||||
styleUrls: ['./avalon.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.Default,
|
||||
})
|
||||
export class AvalonComponent extends AvalonBase implements OnInit {
|
||||
|
||||
AvalonStage = AvalonStage;
|
||||
cardFlipped: boolean = false;
|
||||
gameSize: number = 0;
|
||||
private destroy$: Subject<void> = new Subject<void>();
|
||||
|
||||
private hubConnection: signalR.HubConnection;
|
||||
broadcastFromAPI$ = new Subject<Player>();
|
||||
refreshStage$ = new Subject<AvalonStage>();
|
||||
refreshPlayers$ = new Subject<Player[]>();
|
||||
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private msgBoxService: MsgBoxService,
|
||||
private toastrService: NbToastrService,
|
||||
private headerService: HeaderService,
|
||||
protected cdRef: ChangeDetectorRef,
|
||||
protected avalonService: AvalonService,
|
||||
) {
|
||||
super(cdRef, avalonService);
|
||||
// avalonService.refreshStage$.pipe(takeUntil(this.destroy$)).subscribe(result => {
|
||||
// this.stage = result;
|
||||
|
||||
// });
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.headerService.setHeader('Avalon - 阿瓦隆');
|
||||
|
||||
|
||||
this.avalonService.data = {
|
||||
stage: AvalonStage.JoinGame,
|
||||
players: [],
|
||||
roles: [
|
||||
{
|
||||
enabled: true,
|
||||
isGood: true,
|
||||
role: Role.Merlin,
|
||||
name: 'Merlin (梅林)',
|
||||
images: '',
|
||||
description: '好人- 梅林知道在場所有的壞東西<br>Merlin knows all the Evil players',
|
||||
},
|
||||
{
|
||||
enabled: true,
|
||||
isGood: true,
|
||||
role: Role.Percival,
|
||||
name: 'Percival (波希瓦)',
|
||||
images: '',
|
||||
description: '好人- 波希瓦知道梅林與魔甘娜<br>Percival\'s special power is knowledge of Merlin at the star of the game.',
|
||||
},
|
||||
{
|
||||
enabled: true,
|
||||
isGood: true,
|
||||
role: Role.ArthurKnight,
|
||||
name: 'Loyal Servant of Arthur (亞瑟王的忠貞小夥伴)',
|
||||
images: '',
|
||||
description: '好人- 亞瑟王的忠貞小夥伴<br>Loyal Servant of Arthur',
|
||||
},
|
||||
{
|
||||
enabled: true,
|
||||
isGood: false,
|
||||
role: Role.Morgana,
|
||||
name: 'Morgana (魔甘娜)',
|
||||
images: '',
|
||||
description: '壞人- 能假裝是梅林被波希瓦知道<br>She is on the side of evil. Her power is that she appears to be Merlin - revealing herself to Percival as Merlin.',
|
||||
},
|
||||
{
|
||||
enabled: true,
|
||||
isGood: false,
|
||||
role: Role.Assassin,
|
||||
name: 'Assassin (刺客)',
|
||||
images: '',
|
||||
description: '壞人- 能最後決定刺死誰',
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
isGood: false,
|
||||
role: Role.Oberon,
|
||||
name: 'Oberon (奧伯龍)',
|
||||
images: '',
|
||||
description: '壞人- 不認識自己人也不被自己人認識的邊緣人<br>He is on the side of Evil. His special power is that he does not reveal himself to the other evil players, mor does he gain knowledge of the other evil players at the start of the game.',
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
isGood: false,
|
||||
role: Role.Mordred,
|
||||
name: 'Mordred (默德里德)',
|
||||
images: '',
|
||||
description: '壞人- 不被梅林察覺<br>He is on the side of Evil. His special pwoer is that his identity is not revealed to Merlin at the start of the game.',
|
||||
},
|
||||
{
|
||||
enabled: true,
|
||||
isGood: false,
|
||||
role: Role.Minion,
|
||||
name: 'Minion (壞雜魚)',
|
||||
images: '',
|
||||
description: '壞人- 不要懷疑,你就是個雜魚<br>He is on the side of Evil.',
|
||||
},
|
||||
],
|
||||
questInfos: []
|
||||
};
|
||||
|
||||
this.route.url.pipe(first()).subscribe(url => {
|
||||
|
||||
this.avalonService.isHost = url[0].path.toLowerCase() == 'avalonhost';
|
||||
});
|
||||
|
||||
|
||||
|
||||
this.avalonService.broadcastFromSignalR$.pipe(takeUntil(this.destroy$)).subscribe(result => {
|
||||
this.broadcastFromSignalR(result.action, result.jsonString);
|
||||
//this.cdRef.detectChanges();
|
||||
});
|
||||
this.avalonService.applyCdChange$.pipe(takeUntil(this.destroy$)).subscribe(result => {
|
||||
this.cdRef.detectChanges();
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.broadcastMe('LeaveGame');
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
|
||||
public get hasRole(): boolean {
|
||||
return this.me && this.me.role > 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//#region Actions
|
||||
|
||||
|
||||
getRoleInfoByRole(role: Role) {
|
||||
return this.avalonService.data.roles.find(r => r.role == role);
|
||||
}
|
||||
|
||||
public showMyRole() {
|
||||
let roleInfo = this.getRoleInfoByRole(this.me.role);
|
||||
this.msgBoxService.show(roleInfo.name, roleInfo.description, ADIcon.INFO)
|
||||
}
|
||||
public showMySecret() {
|
||||
//let roleInfo = this.getRoleInfoByRole(this.me.role);
|
||||
|
||||
let description = '';
|
||||
switch (this.me.role) {
|
||||
case Role.Merlin:
|
||||
|
||||
description = `場上壞壞的有 ` + StringUtils.getHtmlBadges(this.players.filter(p => AVALON_EVIL_KNOWN_TO_MERLIN.includes(p.role)).map(p => p.username), 'danger');
|
||||
break;
|
||||
|
||||
case Role.Morgana:
|
||||
case Role.Mordred:
|
||||
case Role.Minion:
|
||||
case Role.Assassin:
|
||||
description = `你的隊友是 ` + StringUtils.getHtmlBadges(this.players.filter(p => AVALON_EVIL_KNOWN_TO_EVIL.includes(p.role)).map(p => p.username), 'info');
|
||||
break;
|
||||
case Role.Percival:
|
||||
|
||||
description = `他倆之中有梅林 ` + StringUtils.getHtmlBadges(this.players.filter(p => AVALON_KNOWN_TO_PERCIVAL.includes(p.role)).map(p => p.username), 'info');
|
||||
break;
|
||||
default:
|
||||
description = '想太多囉~ 你只是個雜魚~~';
|
||||
break;
|
||||
}
|
||||
|
||||
this.msgBoxService.show('噓...', description, ADIcon.INFO);
|
||||
}
|
||||
|
||||
private initializeQuestInfo() {
|
||||
this.avalonService.data.questInfos = [];
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
this.avalonService.data.questInfos.push(
|
||||
{
|
||||
seq: i + 1,
|
||||
teamSize: teamSize[this.gameSize][i],
|
||||
need2Failed: i == 3 && this.players.length >= fourthQuestNeed2Failed,
|
||||
teamMembers: [],
|
||||
success: 0,
|
||||
failed: 0
|
||||
} as QuestInfo
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private rngAssignRoles() {
|
||||
let enabledRoles: Role[] = [];
|
||||
for (let i = 0; i < this.avalonService.data.players.length; i++) {
|
||||
const player = this.avalonService.data.players[i];
|
||||
player.role = Role.NA;
|
||||
}
|
||||
|
||||
enabledRoles.push(...this.avalonService.data.roles.filter(r => r.enabled).map(r => r.role));
|
||||
while (enabledRoles.length < this.avalonService.data.players.length) {
|
||||
enabledRoles.push(Role.ArthurKnight);
|
||||
}
|
||||
while (this.avalonService.data.players.filter(p => p.role == Role.NA).length > 0) {
|
||||
let index = Math.floor(Math.random() * this.avalonService.data.players.filter(p => p.role == Role.NA).length);
|
||||
this.avalonService.data.players.filter(p => p.role == Role.NA)[index].role = enabledRoles.pop();
|
||||
//enabledRoles.push(Role.ArthurKnight);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
previousStage() {
|
||||
|
||||
this.msgBoxService.show('上一棟', '你確定?', ADIcon.QUESTION, ADButtons.YesNo).pipe(first()).subscribe(result => {
|
||||
if (result) {
|
||||
this.avalonService.data.stage -= 1;
|
||||
this.clearVoteStatus();
|
||||
this.broadcastAll();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
nextStage() {
|
||||
switch (this.avalonService.data.stage) {
|
||||
case AvalonStage.JoinGame:
|
||||
if (this.players.length < 5) {
|
||||
this.msgBoxService.show("人數不足!", "遊戲最少需要五人");
|
||||
return;
|
||||
}
|
||||
|
||||
this.gameSize = Math.max(0, this.players.length - 6);
|
||||
|
||||
if (this.avalonService.data.roles.filter(r => r.enabled && false == r.isGood).length != players_evil[this.gameSize]) {
|
||||
this.msgBoxService.show("反派人數錯誤!", "反派人數需要 " + players_evil[this.gameSize].toString() + " 人");
|
||||
return;
|
||||
}
|
||||
|
||||
this.initializeQuestInfo();
|
||||
|
||||
if (this.avalonService.rngAssignRole) {
|
||||
this.rngAssignRoles();
|
||||
this.avalonService.data.stage = AvalonStage.PickTeammate;
|
||||
} else {
|
||||
this.avalonService.data.stage = AvalonStage.NewGame;
|
||||
}
|
||||
|
||||
break;
|
||||
case AvalonStage.TeamVoteResult:
|
||||
let approve = this.players.filter(p => p.teamVote == true).length;
|
||||
if (approve > (this.players.length - approve)) {
|
||||
this.avalonService.data.stage = AvalonStage.QuestVote;
|
||||
} else {
|
||||
this.avalonService.data.stage = AvalonStage.PickTeammate;
|
||||
}
|
||||
break;
|
||||
|
||||
case AvalonStage.QuestVoteResult:
|
||||
this.avalonService.data.stage = AvalonStage.PickTeammate;
|
||||
break;
|
||||
|
||||
default:
|
||||
this.avalonService.data.stage += 1;
|
||||
break;
|
||||
}
|
||||
this.clearVoteStatus();
|
||||
this.broadcastAll();
|
||||
}
|
||||
|
||||
broadcastFromSignalR(action, jsonString) {
|
||||
switch (action) {
|
||||
case 'LeaveGame':
|
||||
const index = this.players.indexOf(this.players.find(p => p.connectionId == jsonString));
|
||||
if (index > -1) {
|
||||
this.players.splice(index, 1);
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
break;
|
||||
|
||||
case 'RefreshPlayers':
|
||||
ObjectUtils.CopyValue(JSON.parse(jsonString), this.avalonService.data.players, [], true);
|
||||
//ObjectUtils.CopyValue(JSON.parse(jsonString), this.players);
|
||||
//this.cdRef.detectChanges();
|
||||
break;
|
||||
case 'RefreshStage':
|
||||
this.avalonService.data.stage = JSON.parse(jsonString);
|
||||
this.clearVoteStatus();
|
||||
break;
|
||||
case 'RefreshAll':
|
||||
//let newData = JSON.parse(jsonString) as GameInfo;
|
||||
//this.avalonService.data.stage = newData.stage;
|
||||
//this.avalonService.data.stage = newData.stage;
|
||||
//ObjectUtils.CopyValue(newData, this.avalonService.data, [], true);
|
||||
this.avalonService.data = JSON.parse(jsonString);
|
||||
break;
|
||||
case 'JoinGame':
|
||||
case 'VoteTeamMate':
|
||||
case 'VoteQuest':
|
||||
default:
|
||||
this.clonePlayerInfoFromJsonString(jsonString);
|
||||
this.detectMoveToNextStage(action);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
|
||||
clonePlayerInfoFromJsonString(jsonString) {
|
||||
let player = JSON.parse(jsonString);
|
||||
let existPlayer = this.players.find(p => p.connectionId == player.connectionId);
|
||||
|
||||
if (existPlayer) {
|
||||
Object.assign(existPlayer, player)
|
||||
}
|
||||
else {
|
||||
this.players.push(player);
|
||||
}
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public get title(): string {
|
||||
switch (this.stage) {
|
||||
case AvalonStage.JoinGame:
|
||||
return '阿瓦隆-加入遊戲';
|
||||
case AvalonStage.NewGame:
|
||||
return '選擇抽到的腳色卡';
|
||||
case AvalonStage.PickTeammate:
|
||||
return '隊長提拔出任務人選';
|
||||
case AvalonStage.TeamVote:
|
||||
return '出任務人選投票階段';
|
||||
case AvalonStage.QuestVote:
|
||||
return '任務投票階段';
|
||||
case AvalonStage.QuestVoteResult:
|
||||
case AvalonStage.TeamVoteResult:
|
||||
return '投票結果';
|
||||
break;
|
||||
|
||||
default:
|
||||
return '';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import { ChangeDetectorRef } from "@angular/core";
|
||||
import { AvalonStage, Player, PlayerAction, QuestInfo, Role, RoleInfo } from "../../entity/Avalon";
|
||||
import { AvalonService } from "../../services/avalon.service";
|
||||
import { StringUtils } from "../../utilities/string-utils";
|
||||
|
||||
export abstract class AvalonBase {
|
||||
|
||||
|
||||
public get id(): string {
|
||||
return this.avalonService.id;
|
||||
}
|
||||
|
||||
public get isHost(): boolean {
|
||||
return this.avalonService.isHost;
|
||||
}
|
||||
|
||||
constructor(
|
||||
protected cdRef: ChangeDetectorRef,
|
||||
protected avalonService: AvalonService
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
public get me(): Player {
|
||||
//let player = this.avalonService.data.players.find(p => p.id == this.avalonService.id);
|
||||
let player = this.avalonService.data.players.find(p => p.connectionId == this.avalonService.connectionId);
|
||||
return player;
|
||||
}
|
||||
|
||||
public get stage(): AvalonStage {
|
||||
return this.avalonService.data.stage;
|
||||
}
|
||||
public get players(): Player[] {
|
||||
return this.avalonService.data.players;
|
||||
}
|
||||
|
||||
public get isLoading(): boolean {
|
||||
return !this.avalonService.data;
|
||||
}
|
||||
public get currentQuest(): QuestInfo {
|
||||
return this.avalonService.data.questInfos.find(q => !q.success && !q.failed);
|
||||
}
|
||||
|
||||
protected broadcastMe(action: PlayerAction) {
|
||||
|
||||
console.log('broadcastMe', this.me.username);
|
||||
this.detectMoveToNextStage(action);
|
||||
this.avalonService.broadcastPlayer(action, this.me);
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
|
||||
protected detectMoveToNextStage(action: PlayerAction): boolean {
|
||||
if (
|
||||
('PickRole' == action && this.players.filter(p => !p.role).length == 0) ||
|
||||
('VoteTeamMate' == action && this.unVoteTeamPlayerList.length == 0) ||
|
||||
('VoteQuest' == action && this.unVoteQuestPlayerList.length == 0)
|
||||
) {
|
||||
let quest = this.currentQuest;
|
||||
if ('VoteTeamMate' == action) {
|
||||
quest.teamMembers = this.players.filter(p => p.goingToQuest == true);
|
||||
quest.approve = this.players.filter(p => p.teamVote == true).length;
|
||||
quest.reject = this.players.filter(p => p.teamVote == false).length;
|
||||
} else if ('VoteQuest' == action) {
|
||||
quest.success = this.players.filter(p => p.goingToQuest == true && p.questVote == true).length;
|
||||
quest.failed = this.players.filter(p => p.goingToQuest == true && p.questVote == false).length;
|
||||
}
|
||||
|
||||
this.avalonService.data.stage = this.avalonService.data.stage + 1;
|
||||
this.clearVoteStatus();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public refreshStage() {
|
||||
//this.stage = stage;
|
||||
|
||||
setTimeout(() => {
|
||||
this.avalonService.broadcast("RefreshStage", this.stage);
|
||||
}, 200);
|
||||
this.cdRef.detectChanges();
|
||||
//this.hubConnection.invoke("Broadcast", "RefreshStage");
|
||||
}
|
||||
|
||||
public refreshAllInfo() {
|
||||
//this.stage = stage;
|
||||
|
||||
this.avalonService.broadcast("RefreshAll", this.avalonService.data);
|
||||
this.cdRef.detectChanges();
|
||||
//this.hubConnection.invoke("Broadcast", "RefreshStage");
|
||||
}
|
||||
|
||||
public broadcastAll() {
|
||||
//this.stage = stage;
|
||||
this.avalonService.broadcast("RefreshAll", this.avalonService.data);
|
||||
this.cdRef.detectChanges();
|
||||
//this.hubConnection.invoke("Broadcast", "RefreshStage");
|
||||
}
|
||||
|
||||
clearVoteStatus() {
|
||||
for (let i = 0; i < this.players.length; i++) {
|
||||
const player = this.players[i];
|
||||
player.questVote = this.stage == AvalonStage.QuestVoteResult ? player.questVote : null;
|
||||
player.teamVote = this.stage == AvalonStage.TeamVoteResult ? player.teamVote : null;
|
||||
//player.goingToQuest = (this.data.stage == AvalonStage.TeamVote || this.data.stage == AvalonStage.QuestVote) ? player.goingToQuest : null;
|
||||
player.role = this.stage != AvalonStage.NewGame ? player.role : null;
|
||||
}
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
|
||||
public get allGoodRoles(): RoleInfo[] {
|
||||
return this.avalonService.data.roles.filter(r => r.isGood);
|
||||
}
|
||||
public get allEvilRoles(): RoleInfo[] {
|
||||
return this.avalonService.data.roles.filter(r => !r.isGood);
|
||||
}
|
||||
public get playerList(): string {
|
||||
return StringUtils.getHtmlBadges(this.players.map(p => p.username), 'primary');
|
||||
}
|
||||
public get unpickPlayerList(): string {
|
||||
return StringUtils.getHtmlBadges(this.players.filter(p => !p.role).map(p => p.username), 'warning');
|
||||
}
|
||||
public get unVoteTeamPlayerList(): string {
|
||||
return StringUtils.getHtmlBadges(this.players.filter(p => p.teamVote == null).map(p => p.username), 'warning');
|
||||
}
|
||||
public get questPlayerList(): string {
|
||||
return StringUtils.getHtmlBadges(this.players.filter(p => p.goingToQuest).map(p => p.username), 'info');
|
||||
}
|
||||
public get unVoteQuestPlayerList(): string {
|
||||
return StringUtils.getHtmlBadges(this.players.filter(p => p.goingToQuest && p.questVote == null).map(p => p.username), 'warning');
|
||||
}
|
||||
|
||||
public get leader(): Player {
|
||||
return this.players.find(p => p.isLeader);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<ng-container *ngIf="null==me.role">
|
||||
<div class="row">
|
||||
<div class="col-{{i>1?'12':'6'}} form-group" *ngFor="let role of allGoodRoles;let i = index">
|
||||
<button nbButton fullWidth hero status="primary" *ngIf="role.enabled"
|
||||
(click)="pickRole(role)">{{role.name}}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-6 form-group" *ngFor="let role of allEvilRoles">
|
||||
<button nbButton fullWidth hero status="danger" *ngIf="role.enabled"
|
||||
(click)="pickRole(role)">{{role.name}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="me.role">
|
||||
<h1>等待遊戲開始中...</h1>
|
||||
|
||||
</ng-container>
|
||||
<div class='form-group'>
|
||||
<label for='unpick' class='label'>還未選擇腳色:</label>
|
||||
<div [innerHtml]="unpickPlayerList"></div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ChooseCharacterComponent } from './choose-character.component';
|
||||
|
||||
describe('ChooseCharacterComponent', () => {
|
||||
let component: ChooseCharacterComponent;
|
||||
let fixture: ComponentFixture<ChooseCharacterComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ ChooseCharacterComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ChooseCharacterComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import { ChangeDetectorRef, Component, Input, NgZone, OnInit } from '@angular/core';
|
||||
import { NbToastrService } from '@nebular/theme';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { GameInfo, Player, Role, RoleInfo } from '../../../entity/Avalon';
|
||||
import { AvalonService } from '../../../services/avalon.service';
|
||||
import { MsgBoxService } from '../../../services/msg-box.service';
|
||||
import { ADButtons, ADIcon } from '../../../ui/alert-dlg/alert-dlg.component';
|
||||
import { AvalonBase } from '../avalonBase';
|
||||
|
||||
@Component({
|
||||
selector: 'avalon-choose-character',
|
||||
templateUrl: './choose-character.component.html',
|
||||
styleUrls: ['./choose-character.component.scss']
|
||||
})
|
||||
export class ChooseCharacterComponent extends AvalonBase implements OnInit {
|
||||
|
||||
|
||||
|
||||
constructor(
|
||||
private msgBoxService: MsgBoxService,
|
||||
protected cdRef: ChangeDetectorRef,
|
||||
private toastrService: NbToastrService,
|
||||
protected avalonService: AvalonService,
|
||||
private ngZone: NgZone
|
||||
) {
|
||||
super(cdRef, avalonService);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
public pickRole(roleInfo: RoleInfo) {
|
||||
|
||||
// this.me.role = roleInfo.role;
|
||||
// this.hasRole = true;
|
||||
// this.broadcastMe('PickRole');
|
||||
// this.cdRef.detectChanges();
|
||||
// return;
|
||||
this.ngZone.run(
|
||||
_ => {
|
||||
this.msgBoxService.show(roleInfo.name, roleInfo.description + '<br>你確定你是?', ADIcon.QUESTION, ADButtons.YesNo).pipe(first()).subscribe(result => {
|
||||
if (result) {
|
||||
if (this.players.find(p => p.role == roleInfo.role) && roleInfo.role != Role.ArthurKnight) {
|
||||
this.toastrService.danger('腳色重複:' + roleInfo.name, '你確定沒有選錯腳色??')
|
||||
} else {
|
||||
this.me.role = roleInfo.role;
|
||||
this.broadcastMe('PickRole');
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
);
|
||||
|
||||
|
||||
}
|
||||
|
||||
// private broadcastMe(action: PlayerAction) {
|
||||
// console.log('broadcastMe', this.me.username);
|
||||
// this.detectMoveToNextStage(action);
|
||||
// this.broadcastPlayer(action, this.me);
|
||||
// this.cdRef.detectChanges();
|
||||
// }
|
||||
|
||||
// detectMoveToNextStage(action: PlayerAction) {
|
||||
// if (
|
||||
// ('VoteTeamMate' == action && this.unVoteTeamPlayerList.length == 0) ||
|
||||
// ('VoteQuest' == action && this.unVoteQuestPlayerList.length == 0)
|
||||
// ) {
|
||||
// this.data.stage = this.data.stage + 1;
|
||||
// this.clearVoteStatus();
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
<ng-container *ngIf="isHost&&me">
|
||||
<nb-toggle name="rngAssignRole" [ngModel]="rngAssignRole" (checkedChange)="setRngAssignRole($event)">
|
||||
自動分派腳色</nb-toggle>
|
||||
<div class="row">
|
||||
<div class="col-md-6" *ngFor="let role of allEvilRoles">
|
||||
<nb-checkbox [(ngModel)]="role.enabled" (checkedChange)="role.enabled=$event">
|
||||
{{role.name}}</nb-checkbox>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="!me;else WaitingMessage">
|
||||
<div class='form-group'>
|
||||
<label for='username' class='label'>Your Name - 名字</label>
|
||||
<input type='text' nbInput fullWidth id='username' name='username' [(ngModel)]='tempUsername'>
|
||||
</div>
|
||||
<!-- <div class='form-group'>
|
||||
<label for='tempSeq' class='label'>Seat No - 座位次序</label>
|
||||
<input type='number' nbInput fullWidth id='tempSeq' name='tempSeq' [(ngModel)]='tempSeq'>
|
||||
</div> -->
|
||||
<button nbButton hero fullWidth status="primary" (click)="tempUsername&&joinGame()">Join
|
||||
Game</button>
|
||||
</ng-container>
|
||||
|
||||
|
||||
<ng-template #WaitingMessage>
|
||||
<h1>等待遊戲開始中...</h1>
|
||||
|
||||
<qr-code *ngIf="isHost" [size]="qrCodeWidth" [value]="'http://happiness.tours/games/avalon'"></qr-code>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { JoinGameComponent } from './join-game.component';
|
||||
|
||||
describe('JoinGameComponent', () => {
|
||||
let component: JoinGameComponent;
|
||||
let fixture: ComponentFixture<JoinGameComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ JoinGameComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(JoinGameComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
import { ChangeDetectorRef, Component, NgZone, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { NbToastrService } from '@nebular/theme';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { AvalonService } from '../../../services/avalon.service';
|
||||
import { SessionService } from '../../../services/session.service';
|
||||
import { AvalonBase } from '../avalonBase';
|
||||
|
||||
@Component({
|
||||
selector: 'avalon-join-game',
|
||||
templateUrl: './join-game.component.html',
|
||||
styleUrls: ['./join-game.component.scss']
|
||||
})
|
||||
export class JoinGameComponent extends AvalonBase implements OnInit {
|
||||
|
||||
tempUsername: string;
|
||||
tempSeq: number;
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
protected cdRef: ChangeDetectorRef,
|
||||
protected avalonService: AvalonService,
|
||||
private ngZone: NgZone,
|
||||
private session: SessionService
|
||||
) {
|
||||
super(cdRef, avalonService);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.queryParamMap.pipe(first()).subscribe(result => {
|
||||
if (result.has('name')) {
|
||||
this.tempUsername = result.get('name');
|
||||
} else {
|
||||
this.tempUsername = this.session.currentUserName;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
joinGame() {
|
||||
this.avalonService.joinGame(this.tempUsername, this.tempSeq);
|
||||
}
|
||||
|
||||
public get rngAssignRole(): boolean {
|
||||
return this.avalonService.rngAssignRole;
|
||||
}
|
||||
|
||||
|
||||
setRngAssignRole(v) {
|
||||
this.avalonService.rngAssignRole = v;
|
||||
}
|
||||
public get qrCodeWidth(): number {
|
||||
return window.innerWidth - 100;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<h2>隊長:{{leader?leader.username + ' 提拔階段':'請選擇初始隊長!'}}</h2>
|
||||
|
||||
<ng-container *ngIf="me.isLeader">
|
||||
<div class="row">
|
||||
<div class="col-md-6 form-group" *ngFor="let p of players;let i=index">
|
||||
|
||||
<nb-checkbox name="goingTo{{i}}" [ngModel]="p.goingToQuest" (checkedChange)="toggleTeamMate(p,$event)">
|
||||
{{p.username}}</nb-checkbox>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<button nbButton hero fullWidth status="primary" class="mt-2" (click)="pickTeamMate()">送出名單</button>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="isHost">
|
||||
<h2>選擇隊長:</h2>
|
||||
<div class="row">
|
||||
<div class="col-6 form-group" *ngFor="let player of players">
|
||||
<button nbButton fullWidth hero status="info" (click)="changeLeader(player)">{{player.username}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PickTeammateComponent } from './pick-teammate.component';
|
||||
|
||||
describe('PickTeammateComponent', () => {
|
||||
let component: PickTeammateComponent;
|
||||
let fixture: ComponentFixture<PickTeammateComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ PickTeammateComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PickTeammateComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,59 @@
|
||||
import { ChangeDetectorRef, Component, NgZone, OnInit } from '@angular/core';
|
||||
import { Player, AvalonStage } from '../../../entity/Avalon';
|
||||
import { AvalonService } from '../../../services/avalon.service';
|
||||
import { MsgBoxService } from '../../../services/msg-box.service';
|
||||
import { ADIcon } from '../../../ui/alert-dlg/alert-dlg.component';
|
||||
import { AvalonBase } from '../avalonBase';
|
||||
|
||||
@Component({
|
||||
selector: 'avalon-pick-teammate',
|
||||
templateUrl: './pick-teammate.component.html',
|
||||
styleUrls: ['./pick-teammate.component.scss']
|
||||
})
|
||||
export class PickTeammateComponent extends AvalonBase implements OnInit {
|
||||
|
||||
constructor(
|
||||
protected cdRef: ChangeDetectorRef,
|
||||
protected avalonService: AvalonService,
|
||||
private ngZone: NgZone,
|
||||
private msgBoxService: MsgBoxService
|
||||
) {
|
||||
super(cdRef, avalonService);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
toggleTeamMate(player: Player, value: boolean) {
|
||||
player.goingToQuest = value;
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
|
||||
public pickTeamMate() {
|
||||
let teamSize = this.players.filter(p => p.goingToQuest).length;
|
||||
if (teamSize == this.currentQuest.teamSize) {
|
||||
this.avalonService.data.stage = AvalonStage.TeamVote;
|
||||
this.clearVoteStatus();
|
||||
this.broadcastAll();
|
||||
this.avalonService.applyCdChange$.next();
|
||||
} else {
|
||||
this.ngZone.run(_ => {
|
||||
this.msgBoxService.show('任務編組錯誤!', `本次任務出場人數為 ${this.currentQuest.teamSize} 人!`, ADIcon.WARNING);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public changeLeader(newLeader: Player) {
|
||||
let oldLeader = this.players.find(p => p.isLeader);
|
||||
if (oldLeader) oldLeader.isLeader = false;
|
||||
newLeader.isLeader = true;
|
||||
|
||||
for (let i = 0; i < this.players.length; i++) {
|
||||
const player = this.players[i];
|
||||
player.goingToQuest = false;
|
||||
}
|
||||
this.refreshAllInfo();
|
||||
this.cdRef.detectChanges();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
<table class="table mb-3" *ngIf="quests">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">任務所需人數</th>
|
||||
<th scope="col">出任隊員</th>
|
||||
<th scope="col">結果</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let q of quests">
|
||||
<th scope="row">{{q.seq}}</th>
|
||||
<td [innerHTML]="teamSize(q)"></td>
|
||||
<td [innerHTML]="teamMembers(q)"></td>
|
||||
<td [innerHTML]="questResult(q)"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<button nbButton hero fullWidth status="primary" *ngIf="isHost" (click)="newGame()">New Game</button>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { QuestTableComponent } from './quest-table.component';
|
||||
|
||||
describe('QuestTableComponent', () => {
|
||||
let component: QuestTableComponent;
|
||||
let fixture: ComponentFixture<QuestTableComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ QuestTableComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(QuestTableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,67 @@
|
||||
import { ChangeDetectorRef, Component, NgZone, OnInit } from '@angular/core';
|
||||
import { AvalonStage, QuestInfo } from '../../../entity/Avalon';
|
||||
import { AvalonService } from '../../../services/avalon.service';
|
||||
import { StringUtils } from '../../../utilities/string-utils';
|
||||
import { AvalonBase } from '../avalonBase';
|
||||
|
||||
@Component({
|
||||
selector: 'avalon-quest-table',
|
||||
templateUrl: './quest-table.component.html',
|
||||
styleUrls: ['./quest-table.component.scss']
|
||||
})
|
||||
export class QuestTableComponent extends AvalonBase implements OnInit {
|
||||
|
||||
constructor(
|
||||
protected cdRef: ChangeDetectorRef,
|
||||
protected avalonService: AvalonService,
|
||||
private ngZone: NgZone
|
||||
) {
|
||||
super(cdRef, avalonService);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
|
||||
public get quests(): QuestInfo[] {
|
||||
if (this.avalonService.data && this.avalonService.data.questInfos.length > 0) {
|
||||
return this.avalonService.data.questInfos;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public teamSize(q: QuestInfo): string {
|
||||
let result = q.teamSize.toString();
|
||||
if (q.approve > 0) {
|
||||
result += ' :' + StringUtils.getHtmlBadge(q.approve.toString(), "light");
|
||||
}
|
||||
if (q.reject > 0) {
|
||||
result += StringUtils.getHtmlBadge(q.reject.toString(), "dark");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public teamMembers(q: QuestInfo): string {
|
||||
if (q.teamMembers.length == 0) return "";
|
||||
return StringUtils.getHtmlBadges(q.teamMembers.map(t => t.username), 'info');
|
||||
}
|
||||
|
||||
|
||||
public questResult(q: QuestInfo): string {
|
||||
let result = "";
|
||||
if (q.success > 0) {
|
||||
result += StringUtils.getHtmlBadge(q.success.toString(), "info");
|
||||
}
|
||||
if (q.failed > 0) {
|
||||
result += StringUtils.getHtmlBadge(q.failed.toString(), "danger");
|
||||
}
|
||||
if (q.need2Failed) result += StringUtils.getHtmlBadge("2 Failed", "warning");
|
||||
return result;
|
||||
}
|
||||
|
||||
newGame() {
|
||||
this.avalonService.data.stage = AvalonStage.JoinGame;
|
||||
this.avalonService.data.questInfos = [];
|
||||
this.broadcastAll();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<h2>由出任務者投票</h2>
|
||||
<div class="fa-2x my-4" [innerHtml]="questPlayerList"></div>
|
||||
<ng-container *ngIf="me.goingToQuest;else WaitingMessage">
|
||||
|
||||
<div class="row" *ngIf="me.questVote==null">
|
||||
<div class="col-12 form-group">
|
||||
<button nbButton fullWidth hero status="success" (click)="voteQuestVote(true)">是的,有我出馬,馬到渠成!</button>
|
||||
</div>
|
||||
<div class="col-12 form-group">
|
||||
<button nbButton fullWidth hero status="danger" (click)="voteQuestVote(false)">當然不,搞怪囉!</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</ng-container>
|
||||
|
||||
<ng-template #WaitingMessage>
|
||||
<h1>等待遊戲開始中...</h1>
|
||||
</ng-template>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { QuestVoteComponent } from './quest-vote.component';
|
||||
|
||||
describe('QuestVoteComponent', () => {
|
||||
let component: QuestVoteComponent;
|
||||
let fixture: ComponentFixture<QuestVoteComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ QuestVoteComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(QuestVoteComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import { ChangeDetectorRef, Component, NgZone, OnInit } from '@angular/core';
|
||||
import { AvalonService } from '../../../services/avalon.service';
|
||||
import { AvalonBase } from '../avalonBase';
|
||||
|
||||
@Component({
|
||||
selector: 'avalon-quest-vote',
|
||||
templateUrl: './quest-vote.component.html',
|
||||
styleUrls: ['./quest-vote.component.scss']
|
||||
})
|
||||
export class QuestVoteComponent extends AvalonBase implements OnInit {
|
||||
|
||||
constructor(
|
||||
protected cdRef: ChangeDetectorRef,
|
||||
protected avalonService: AvalonService,
|
||||
private ngZone: NgZone
|
||||
) {
|
||||
super(cdRef, avalonService);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
|
||||
public voteQuestVote(value: boolean) {
|
||||
this.me.questVote = value;
|
||||
this.broadcastMe('VoteQuest');
|
||||
this.avalonService.applyCdChange$.next();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<h2>你是否相信以下成員出任務?</h2>
|
||||
<div class="fa-2x my-4" [innerHtml]="questPlayerList"></div>
|
||||
|
||||
<div class="row" *ngIf="me.teamVote==null">
|
||||
<div class="col-12 form-group">
|
||||
<button nbButton fullWidth hero status="success" (click)="voteTeamVote(true)">是的,我相信我的小夥伴們</button>
|
||||
</div>
|
||||
<div class="col-12 form-group">
|
||||
<button nbButton fullWidth hero status="danger" (click)="voteTeamVote(false)">想出去?
|
||||
想太多囉!</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class='form-group'>
|
||||
<label for='unpick' class='label'>還未投票:</label>
|
||||
<div [innerHtml]="unVoteTeamPlayerList"></div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TeamVoteComponent } from './team-vote.component';
|
||||
|
||||
describe('TeamVoteComponent', () => {
|
||||
let component: TeamVoteComponent;
|
||||
let fixture: ComponentFixture<TeamVoteComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ TeamVoteComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TeamVoteComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { ChangeDetectorRef, Component, NgZone, OnInit } from '@angular/core';
|
||||
import { AvalonService } from '../../../services/avalon.service';
|
||||
import { AvalonBase } from '../avalonBase';
|
||||
|
||||
@Component({
|
||||
selector: 'avalon-team-vote',
|
||||
templateUrl: './team-vote.component.html',
|
||||
styleUrls: ['./team-vote.component.scss']
|
||||
})
|
||||
export class TeamVoteComponent extends AvalonBase implements OnInit {
|
||||
|
||||
constructor(
|
||||
protected cdRef: ChangeDetectorRef,
|
||||
protected avalonService: AvalonService,
|
||||
private ngZone: NgZone
|
||||
) {
|
||||
super(cdRef, avalonService);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
}
|
||||
|
||||
public voteTeamVote(value: boolean) {
|
||||
this.me.teamVote = value;
|
||||
this.broadcastMe('VoteTeamMate');
|
||||
this.avalonService.applyCdChange$.next();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
<label class="badge badge-info fa-2x">{{successText}}:{{success}}</label>
|
||||
<label class="badge badge-danger fa-2x">{{failedText}}:{{failed}}</label>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { VoteResultComponent } from './vote-result.component';
|
||||
|
||||
describe('VoteResultComponent', () => {
|
||||
let component: VoteResultComponent;
|
||||
let fixture: ComponentFixture<VoteResultComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ VoteResultComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(VoteResultComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import { ChangeDetectorRef, Component, Input, NgZone, OnInit } from '@angular/core';
|
||||
import { AvalonService } from '../../../services/avalon.service';
|
||||
import { AvalonBase } from '../avalonBase';
|
||||
|
||||
@Component({
|
||||
selector: 'avalon-vote-result',
|
||||
templateUrl: './vote-result.component.html',
|
||||
styleUrls: ['./vote-result.component.scss']
|
||||
})
|
||||
export class VoteResultComponent extends AvalonBase implements OnInit {
|
||||
|
||||
@Input() isTeamVote: boolean = true;
|
||||
constructor(
|
||||
protected cdRef: ChangeDetectorRef,
|
||||
protected avalonService: AvalonService,
|
||||
private ngZone: NgZone
|
||||
) {
|
||||
super(cdRef, avalonService);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public get successText(): string {
|
||||
return this.isTeamVote ? '贊成(Approve)' : '成功(Success)';
|
||||
}
|
||||
|
||||
public get failedText(): string {
|
||||
return this.isTeamVote ? '反對(Reject)' : '失敗(Failed)';
|
||||
}
|
||||
|
||||
success: number = 0;
|
||||
failed: number = 0;
|
||||
ngOnInit(): void {
|
||||
if (this.isTeamVote) {
|
||||
this.success = this.players.filter(p => p.teamVote == true).length;
|
||||
this.failed = this.players.length - this.success;
|
||||
} else {
|
||||
|
||||
this.success = this.players.filter(p => p.goingToQuest && p.questVote == true).length;
|
||||
this.failed = this.players.filter(p => p.goingToQuest && p.questVote == false).length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { AvalonComponent } from './avalon/avalon.component';
|
||||
import { GamesComponent } from './games.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '', component: GamesComponent
|
||||
,
|
||||
|
||||
children:
|
||||
[
|
||||
{ path: 'avalon', component: AvalonComponent },
|
||||
{ path: 'avalonHost', component: AvalonComponent },
|
||||
]
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class GamesRoutingModule { }
|
||||
@@ -0,0 +1,4 @@
|
||||
<ngx-one-column-layout>
|
||||
<!-- <nb-menu [items]="MENU_ITEMS"></nb-menu> -->
|
||||
<router-outlet></router-outlet>
|
||||
</ngx-one-column-layout>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { GamesComponent } from './games.component';
|
||||
|
||||
describe('GamesComponent', () => {
|
||||
let component: GamesComponent;
|
||||
let fixture: ComponentFixture<GamesComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ GamesComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(GamesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Title } from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'ngx-games',
|
||||
templateUrl: './games.component.html',
|
||||
styleUrls: ['./games.component.scss']
|
||||
})
|
||||
export class GamesComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit(): void {
|
||||
//this.browserTitleService.setTitle('我是遊戲王');
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { QRCodeModule } from 'angular2-qrcode';
|
||||
import { GamesRoutingModule } from './games-routing.module';
|
||||
import { GamesComponent } from './games.component';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { NbMenuModule, NbInputModule, NbCardModule, NbButtonModule, NbActionsModule, NbCheckboxModule, NbRadioModule, NbDatepickerModule, NbSelectModule, NbIconModule, NbTagModule, NbStepperModule, NbListModule, NbUserModule, NbSpinnerModule, NbDialogModule, NbToggleModule } from '@nebular/theme';
|
||||
import { ThemeModule } from '../@theme/theme.module';
|
||||
import { AdminRoutingModule } from '../admin/admin-routing.module';
|
||||
import { AlertDlgModule } from '../ui/alert-dlg/alert-dlg.module';
|
||||
import { AvalonComponent } from './avalon/avalon.component';
|
||||
import { ChooseCharacterComponent } from './avalon/choose-character/choose-character.component';
|
||||
import { JoinGameComponent } from './avalon/join-game/join-game.component';
|
||||
import { PickTeammateComponent } from './avalon/pick-teammate/pick-teammate.component';
|
||||
import { TeamVoteComponent } from './avalon/team-vote/team-vote.component';
|
||||
import { QuestVoteComponent } from './avalon/quest-vote/quest-vote.component';
|
||||
import { VoteResultComponent } from './avalon/vote-result/vote-result.component';
|
||||
import { QuestTableComponent } from './avalon/quest-table/quest-table.component';
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
GamesComponent,
|
||||
AvalonComponent,
|
||||
ChooseCharacterComponent,
|
||||
JoinGameComponent,
|
||||
PickTeammateComponent,
|
||||
TeamVoteComponent,
|
||||
QuestVoteComponent,
|
||||
VoteResultComponent,
|
||||
QuestTableComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
GamesRoutingModule,
|
||||
FormsModule,
|
||||
AdminRoutingModule,
|
||||
ThemeModule,
|
||||
NbMenuModule,
|
||||
NbInputModule,
|
||||
NbCardModule,
|
||||
NbButtonModule,
|
||||
NbActionsModule,
|
||||
NbCheckboxModule,
|
||||
NbRadioModule,
|
||||
NbDatepickerModule,
|
||||
NbSelectModule,
|
||||
NbIconModule,
|
||||
NbTagModule,
|
||||
NbStepperModule,
|
||||
NbListModule,
|
||||
NbToggleModule,
|
||||
NbUserModule,
|
||||
NbSpinnerModule,
|
||||
NbDialogModule.forRoot(),
|
||||
AlertDlgModule,
|
||||
QRCodeModule,
|
||||
]
|
||||
})
|
||||
export class GamesModule { }
|
||||
Reference in New Issue
Block a user