send fire emoji to other players

This commit is contained in:
evanpelle
2024-10-04 13:08:20 -07:00
parent b35e78f2f8
commit e795b22220
13 changed files with 182 additions and 27 deletions
+16 -3
View File
@@ -1,5 +1,5 @@
import {z} from 'zod';
import {PlayerType} from './game/Game';
import {Emoji, PlayerType} from './game/Game';
export type GameID = string
export type ClientID = string
@@ -12,6 +12,7 @@ export type Intent = SpawnIntent
| AllianceRequestReplyIntent
| BreakAllianceIntent
| TargetPlayerIntent
| EmojiIntent
export type AttackIntent = z.infer<typeof AttackIntentSchema>
export type SpawnIntent = z.infer<typeof SpawnIntentSchema>
@@ -21,7 +22,7 @@ export type AllianceRequestIntent = z.infer<typeof AllianceRequestIntentSchema>
export type AllianceRequestReplyIntent = z.infer<typeof AllianceRequestReplyIntentSchema>
export type BreakAllianceIntent = z.infer<typeof BreakAllianceIntentSchema>
export type TargetPlayerIntent = z.infer<typeof TargetPlayerIntentSchema>
export type EmojiIntent = z.infer<typeof EmojiIntentSchema>
export type Turn = z.infer<typeof TurnSchema>
@@ -37,6 +38,8 @@ export type ClientJoinMessage = z.infer<typeof ClientJoinMessageSchema>
export type ClientLeaveMessage = z.infer<typeof ClientLeaveMessageSchema>
const PlayerTypeSchema = z.nativeEnum(PlayerType);
const EmojiSchema = z.nativeEnum(Emoji);
// TODO: create Cell schema
@@ -46,9 +49,11 @@ export interface Lobby {
numClients: number;
}
// Zod schemas
const BaseIntentSchema = z.object({
type: z.enum(['attack', 'spawn', 'boat', 'name']),
type: z.enum(['attack', 'spawn', 'boat', 'name', 'targetPlayer', 'emoji']),
clientID: z.string(),
});
@@ -112,6 +117,13 @@ export const TargetPlayerIntentSchema = BaseIntentSchema.extend({
target: z.string(),
})
export const EmojiIntentSchema = BaseIntentSchema.extend({
type: z.literal('emoji'),
sender: z.string(),
recipient: z.string(),
emoji: EmojiSchema,
})
const IntentSchema = z.union([
AttackIntentSchema,
SpawnIntentSchema,
@@ -121,6 +133,7 @@ const IntentSchema = z.union([
AllianceRequestReplyIntentSchema,
BreakAllianceIntentSchema,
TargetPlayerIntentSchema,
EmojiIntentSchema,
]);
const TurnSchema = z.object({
+2
View File
@@ -51,6 +51,8 @@ export interface Config {
allianceRequestCooldown(): Tick
targetDuration(): Tick
targetCooldown(): Tick
emojiMessageCooldown(): Tick
emojiMessageDuration(): Tick
}
export interface Theme {
+6
View File
@@ -7,6 +7,12 @@ import {pastelTheme} from "./PastelTheme";
export class DefaultConfig implements Config {
emojiMessageDuration(): Tick {
return 5 * 10
}
emojiMessageCooldown(): Tick {
return 15 * 10
}
targetDuration(): Tick {
return 10 * 10
}
+7 -7
View File
@@ -5,14 +5,14 @@ export const devConfig = new class extends DefaultConfig {
return 95
}
numSpawnPhaseTurns(): number {
return 40
}
gameCreationRate(): number {
return 2 * 1000
}
lobbyLifetime(): number {
return 2 * 1000
return 80
}
// gameCreationRate(): number {
// return 2 * 1000
// }
// lobbyLifetime(): number {
// return 2 * 1000
// }
turnIntervalMs(): number {
return 100
}
+43
View File
@@ -0,0 +1,43 @@
import {AllPlayers, Emoji, Execution, MutableGame, MutablePlayer, PlayerID} from "../game/Game";
export class EmojiExecution implements Execution {
private requestor: MutablePlayer
private recipient: MutablePlayer | typeof AllPlayers
private active = true
constructor(
private senderID: PlayerID,
private recipientID: PlayerID | typeof AllPlayers,
private emoji: Emoji
) { }
init(mg: MutableGame, ticks: number): void {
this.requestor = mg.player(this.senderID)
this.recipient = this.recipientID == AllPlayers ? AllPlayers : mg.player(this.recipientID)
}
tick(ticks: number): void {
if (this.requestor.canSendEmoji(this.recipient)) {
this.requestor.sendEmoji(this.recipient, this.emoji)
} else {
console.warn(`cannot send emoji from ${this.requestor} to ${this.recipient}`)
}
this.active = false
}
owner(): MutablePlayer {
return null
}
isActive(): boolean {
return this.active
}
activeDuringSpawnPhase(): boolean {
return false
}
}
+3
View File
@@ -13,6 +13,7 @@ import {AllianceRequestExecution} from "./alliance/AllianceRequestExecution";
import {AllianceRequestReplyExecution} from "./alliance/AllianceRequestReplyExecution";
import {BreakAllianceExecution} from "./alliance/BreakAllianceExecution";
import {TargetPlayerExecution} from "./TargetPlayerExecution";
import {EmojiExecution} from "./EmojiExecution";
@@ -67,6 +68,8 @@ export class Executor {
return new BreakAllianceExecution(intent.requestor, intent.recipient)
} else if (intent.type == "targetPlayer") {
return new TargetPlayerExecution(intent.requestor, intent.target)
} else if (intent.type == "emoji") {
return new EmojiExecution(intent.sender, intent.recipient, intent.emoji)
}
else {
throw new Error(`intent type ${intent} not found`)
+28
View File
@@ -8,6 +8,26 @@ import {BreakAllianceExecution} from "../execution/alliance/BreakAllianceExecuti
export type PlayerID = string
export type Tick = number
export enum Emoji {
ThumbsUp = "👍",
ThumbsDown = "👎",
Smile = "😊",
Sad = "😢",
Heart = "❤️",
Fire = "🔥",
}
export const AllPlayers = "AllPlayers" as const;
export class EmojiMessage {
constructor(
public readonly sender: Player,
public readonly recipient: Player | typeof AllPlayers,
public readonly emoji: Emoji,
public readonly createdAt: Tick
) { }
}
export class Cell {
private strRepr: string
@@ -156,6 +176,8 @@ export interface Player {
// Targets of player and all allies.
transitiveTargets(): Player[]
toString(): string
canSendEmoji(recipient: Player | typeof AllPlayers): boolean
outgoingEmojis(): EmojiMessage[]
}
export interface MutablePlayer extends Player {
@@ -178,6 +200,8 @@ export interface MutablePlayer extends Player {
target(other: Player): void
targets(): MutablePlayer[]
transitiveTargets(): MutablePlayer[]
// Null means send to all Players
sendEmoji(recipient: Player | typeof AllPlayers, emoji: Emoji): void
}
export interface Game {
@@ -241,4 +265,8 @@ export class AllianceExpiredEvent implements GameEvent {
export class TargetPlayerEvent implements GameEvent {
constructor(public readonly player: Player, public readonly target: Player) { }
}
export class EmojiMessageEvent implements GameEvent {
constructor(public readonly message: EmojiMessage) { }
}
+26 -1
View File
@@ -1,4 +1,4 @@
import {MutablePlayer, Tile, PlayerInfo, PlayerID, PlayerType, Player, TerraNullius, Cell, MutableGame, Execution, AllianceRequest, MutableAllianceRequest, MutableAlliance, Alliance, Tick, TargetPlayerEvent} from "./Game";
import {MutablePlayer, Tile, PlayerInfo, PlayerID, PlayerType, Player, TerraNullius, Cell, MutableGame, Execution, AllianceRequest, MutableAllianceRequest, MutableAlliance, Alliance, Tick, TargetPlayerEvent, Emoji, EmojiMessage, EmojiMessageEvent, AllPlayers} from "./Game";
import {ClientID} from "../Schemas";
import {simpleHash} from "../Util";
import {CellString, GameImpl} from "./GameImpl";
@@ -26,6 +26,8 @@ export class PlayerImpl implements MutablePlayer {
private targets_: Target[] = []
private outgoingEmojis_: EmojiMessage[] = []
constructor(private gs: GameImpl, private readonly playerInfo: PlayerInfo, private _troops) {
this._name = playerInfo.name;
}
@@ -207,6 +209,29 @@ export class PlayerImpl implements MutablePlayer {
return [...new Set(ts)]
}
sendEmoji(recipient: Player | typeof AllPlayers, emoji: Emoji): void {
if (recipient == this) {
throw Error(`Cannot send emoji to oneself: ${this}`)
}
const msg = new EmojiMessage(this, recipient, emoji, this.gs.ticks())
this.outgoingEmojis_.push(msg)
this.gs.eventBus.emit(new EmojiMessageEvent(msg))
}
outgoingEmojis(): EmojiMessage[] {
return null
}
canSendEmoji(recipient: Player | null): boolean {
const prevMsgs = this.outgoingEmojis_.filter(msg => msg.recipient == recipient)
for (const msg of prevMsgs) {
if (this.gs.ticks() - msg.createdAt < this.gs.config().emojiMessageCooldown()) {
return false
}
}
return true
}
hash(): number {
return simpleHash(this.id()) * (this.troops() + this.numTilesOwned());
}