Skip to content

Commit

Permalink
SR gacha improvement
Browse files Browse the repository at this point in the history
  • Loading branch information
stdcall0 committed Apr 15, 2024
1 parent 2a192a1 commit 1d02455
Show file tree
Hide file tree
Showing 8 changed files with 391 additions and 181 deletions.
130 changes: 57 additions & 73 deletions apps/starrail_gacha.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,9 @@
import { Plugin, Common, Lottery } from '#gc';

import { StarRail } from "#gc.model";

const fiveStarUp = new Lottery([new StarRail.FiveStarItem("Acheron", "黄泉", "")]);
const fourStarUp = new Lottery([
new StarRail.FourStarItem("41", "王俊超A", ""),
new StarRail.FourStarItem("42", "王俊超B", ""),
new StarRail.FourStarItem("43", "王俊超C", ""),
]);
const fiveStarNorm = new Lottery([
new StarRail.FiveStarItem("Welt", "瓦尔特", ""),
new StarRail.FiveStarItem("Himeko", "姬子", ""),
new StarRail.FiveStarItem("Bronya", "布洛妮娅", ""),
new StarRail.FiveStarItem("Gepard", "杰帕德", ""),
new StarRail.FiveStarItem("Clara", "克拉拉", ""),
new StarRail.FiveStarItem("Yanqing", "彦卿", ""),
new StarRail.FiveStarItem("Bailu", "白露", ""),
]);

let pool = new StarRail.GachaPool(fiveStarUp, fiveStarNorm, fourStarUp,
new Lottery([new StarRail.ThreeStarItem("垃圾", "垃圾", "")]));

let last5: { [key: string]: number } = {};
let last4: { [key: string]: number } = {};
let upg: { [key: string]: boolean } = {};
import { Plugin, Common } from '#gc';

import { StarRail } from '#gc.model';
import { StarRailData } from '#gc.res';

let state: { [key: string]: StarRail.GachaState } = {};

export class SRGachaPlugin extends Plugin {
constructor() {
Expand All @@ -49,83 +29,87 @@ export class SRGachaPlugin extends Plugin {
});
}

private getGachaState(key: string): StarRail.GachaState {
// get last5 and last4 from global variable
// default 0 if not exist
let l5 = last5[key] || 0;
let l4 = last4[key] || 0;
let up = upg[key] || false;
return new StarRail.GachaState(pool, l4, l5, up);
private getGacha(key: string): StarRail.Gacha {
const s = state[key] || StarRail.defaultGachaState;
return new StarRail.Gacha(s);
}
private saveGachaState(key: string, state: StarRail.GachaState) {
// save last5 and last4 to global variable
last5[key] = state.lastFive;
last4[key] = state.lastFour;
upg[key] = state.upGuaranteed;
private saveGacha(key: string, gacha: StarRail.Gacha) {
state[key] = gacha.state();
}

item(i: StarRail.GachaItem) {
if (i.star == 5) return `[[ ${i.displayName} ]]`;
if (i.star == 4) return `[ ${i.displayName} ]`;
return i.displayName;
item(x: StarRail.GachaItem): string {
if (x.star > 3) return `${x.star}* ${x.displayName}`;
return x.displayName;
}

async n(n: number, state: StarRail.GachaState, summary: boolean): Promise<StarRail.GachaState> {
let res = state.nextMulti(n);
async n(n: number, gacha: StarRail.Gacha, summary: boolean): Promise<StarRail.Gacha> {
let res = gacha.nextMulti(StarRailData.Pool, n);

// print 10 results per line
let msg: string[] = [];
let msg: string[] = ["详情: "];
let resStr = "";

let cnt5 = 0;

let s5 = [], s4 = [];

for (let i = 0; i < res.length; ++i) {
resStr += this.item(res[i]) + " ";
resStr += this.item(res[i].item) + ", ";
if (i % 10 == 9) {
msg.push(resStr);
msg.push(`${cnt5}~${res[i].count5}: ${resStr}`);
resStr = "";
}
if (i % 10 == 0) {
cnt5 = res[i].count5;
}

if (res[i].item.star == 5) s5.push(res[i]);
if (res[i].item.star == 4) s4.push(res[i]);
}
if (resStr.length > 0) msg.push(resStr);

if (resStr.length > 0)
msg.push(`${cnt5}~${res[res.length - 1].count5}: ${resStr}`);

if (summary) {
msg.push("");
let summary = "5*: ";
// show 5* with format name xcount
let five = res.filter(x => x.star == 5);
let fiveMap = new Map<string, number>();
five.forEach(x => {
let name = x.displayName;
let count = fiveMap.get(name) || 0;
fiveMap.set(name, count + 1);
});
fiveMap.forEach((v, k) => {
summary += `[${k}]x${v} `;
});
msg.push(summary);
msg.push(""); msg.push("统计: ");

if (s5.length > 0) {
msg.push("5*: ");
s5.forEach(x => {
msg.push(`- ${x.item.displayName} (${x.count5})`);
});
}
if (s5.length > 0 && s4.length > 0) msg.push("");
if (s4.length > 0) {
msg.push("4*: ");
s4.forEach(x => {
msg.push(`- ${x.item.displayName} (${x.count5} >> ${x.count})`);
});
}
}

msg.push(`目前已垫 ${state.lastFive} 抽`);
await this.reply(msg.join('\n'), true, { at: false, recallMsg: 0 });
return state;
return gacha;
}

async single() {
const key = this.e.user_id;
let state = this.getGachaState(key);
state = await this.n(1, state, false);
this.saveGachaState(key, state);
let gacha = this.getGacha(key);
gacha = await this.n(1, gacha, false);
this.saveGacha(key, gacha);
}

async ten() {
const key = this.e.user_id;
let state = this.getGachaState(key);
state = await this.n(10, state, false);
this.saveGachaState(key, state);
let gacha = this.getGacha(key);
gacha = await this.n(10, gacha, true);
this.saveGacha(key, gacha);
}

async hundred() {
const key = this.e.user_id;
let state = this.getGachaState(key);
state = await this.n(100, state, true);
this.saveGachaState(key, state);
let gacha = this.getGacha(key);
gacha = await this.n(100, gacha, true);
this.saveGacha(key, gacha);
}
};
195 changes: 135 additions & 60 deletions model/starrail/gacha.ts
Original file line number Diff line number Diff line change
@@ -1,80 +1,155 @@
import { Lottery } from "#gc";
import { GachaItem } from "./gachaitem.js";

export class GachaPool {
export interface GachaResult {
item: GachaItem;
count: number;
count5?: number;
isGuaranteed: boolean;
};

export class GachaSubPool {
protected lot: Lottery<GachaItem>;

constructor(
public fiveStarUpItems: Lottery<GachaItem>,
public fiveStarPermItems: Lottery<GachaItem>,
public fourStarItems: Lottery<GachaItem>,
public threeStarItems: Lottery<GachaItem>
public items: GachaItem[]
) {
this.lot = new Lottery(items);
}

next(): GachaResult {
const item = this.lot.choice();
return {
item,
count: 0,
isGuaranteed: false
};
}
};

export class GachaState {
lastFour: number; // 4* is guaranteed each 10 rolls
lastFive: number;
upGuaranteed: boolean; // if 5* character is not the up one, it will be guaranteed next time
pool: GachaPool;

constructor(pool: GachaPool, lastFour?: number, lastFive?: number, upGuaranteed?: boolean) {
this.pool = pool;
this.lastFour = lastFour || 0;
this.lastFive = lastFive || 0;
this.upGuaranteed = upGuaranteed || false;
export class GachaSubPoolUp {
protected lot: Lottery<GachaItem>;
private lotUp: Lottery<GachaItem>;

constructor(
public items: GachaItem[]
) {
this.lot = new Lottery(items);
this.lotUp = this.lot.filter(i => i.up);
}

get weight(): number[] {
const five = this.lastFive <= 72 ? 60 : 60 + 600 * (this.lastFive - 72);
const four = this.lastFour <= 7 ? 510 : 510 + 5100 * (this.lastFour - 7);
if (this.upGuaranteed)
return [
five, // 5* up
0, // 5 * perm
four, // 4* item
9430 // 3* item
];
else
return [
five / 2, // 5* up
five / 2, // 5 * perm
four, // 4* item
9430 // 3* item
];
next(upGuaranteed: boolean): GachaResult {
if (upGuaranteed) {
const item = this.lotUp.choice();
return {
item,
count: 0,
isGuaranteed: true
};
} else {
const item = this.lot.choice();
return {
item,
count: 0,
isGuaranteed: false
};
}
}
};

export interface GachaPool {
five: GachaSubPoolUp;
four: GachaSubPoolUp;
three: GachaSubPool;
}

export interface GachaState {
last5: number;
last4: number;

up5Guaranteed: boolean;
up4Guaranteed: boolean;
};

get lottery(): Lottery<Lottery<GachaItem>> {
return new Lottery([
this.pool.fiveStarUpItems,
this.pool.fiveStarPermItems,
this.pool.fourStarItems,
this.pool.threeStarItems
], this.weight);
export const defaultGachaState: GachaState = {
last5: 0,
last4: 0,
up5Guaranteed: false,
up4Guaranteed: false
};

export class Gacha {
private s: GachaState;

constructor(s: GachaState = defaultGachaState) {
this.s = s;
}
state(): GachaState {
return this.s;
}

next(): GachaItem {
const _lot = this.lottery;
const itemsIndex = _lot.choiceIndex();
get weight(): number[] {
const five = this.s.last5 <= 72 ? 60 : 60 + 600 * (this.s.last5 - 72);
const four = this.s.last4 <= 7 ? 510 : 510 + 5100 * (this.s.last4 - 7);
return [
five, // 5*
four, // 4*
9430 // 3*
];
}

const item = _lot.objList[itemsIndex].choice();
next(pool: GachaPool): GachaResult {
const lot = new Lottery([5, 4, 3], this.weight);
const sub = lot.choice();

if (itemsIndex <= 1) {
this.lastFive = 0;
this.lastFour += 1;

this.upGuaranteed = itemsIndex == 1;
} else if (itemsIndex == 2) {
this.lastFour = 0;
this.lastFive += 1;
} else if (itemsIndex == 3) {
this.lastFive += 1;
this.lastFour += 1;
} else throw new Error("Invalid item index");

return item;
if (sub == 5) {
let res = pool.five.next(this.s.up5Guaranteed);
if (res.isGuaranteed) {
this.s.up5Guaranteed = false;
} else {
this.s.up5Guaranteed = !res.item.up;
}

res = {
...res,
count5: this.s.last5 + 1,
count: this.s.last5 + 1
};

this.s.last5 = 0;
this.s.last4++;

return res;
} else if (sub == 4) {
let res = pool.four.next(this.s.up4Guaranteed);
if (res.isGuaranteed) {
this.s.up4Guaranteed = false;
} else {
this.s.up4Guaranteed = !res.item.up;
}

res = {
...res,
count5: this.s.last5 + 1,
count: this.s.last4 + 1
};

this.s.last5++;
this.s.last4 = 0;

return res;
} else {
this.s.last5++;
this.s.last4++;

return {
...pool.three.next(),
count5: this.s.last5
};
}
}

nextMulti(n: number): GachaItem[] {
return Array.from({length: n}, this.next.bind(this));
nextMulti(pool: GachaPool, n: number): GachaResult[] {
return Array.from({length: n}, this.next.bind(this, pool));
}
};
Loading

0 comments on commit 1d02455

Please sign in to comment.