Skip to content

Instantly share code, notes, and snippets.

@parrot-studio
Last active November 26, 2019 08:11
Show Gist options
  • Save parrot-studio/697b7a5e04168cfa7ddac6f6e2a876d1 to your computer and use it in GitHub Desktop.
Save parrot-studio/697b7a5e04168cfa7ddac6f6e2a876d1 to your computer and use it in GitHub Desktop.
ccptsのドメイン設計

ccptsドメイン設計

ドメイン

  • チェンクロ パーティーシミュレーター(ccpts)
    • パーティー編集:コアドメイン
      • パーティー編集コンテキスト
    • アルカナ検索:サブドメイン
      • アルカナ検索コンテキスト
      • アルカナ検索サーバコンテキスト

パーティー編集コンテキスト

NOTE:
「入れ替えができる」等の機能仕様=アプリケーション(ccpts)の仕様
ドメインモデルで定義するのは「チェンクロのPTが満たす制約」

プレイヤー:(ドメイン範囲外)

チェインクロニクルで遊ぶ人であり、ccptsの利用者
パーティーを編集したい人

キャラクター:(ドメイン範囲外)

ストーリーの登場人物
ストーリーのある時点でのキャラクターにパラメータが与えられたものがアルカナ
ゲーム内では親愛度を管理する単位

以前は「同一のキャラクターは一人しかPTに編集できない」という制約があったが、現在は存在しないので、ドメインの範囲外

アルカナ(Arcana):値オブジェクト・集約ルート

プレイヤーが所有する、バトルに参加可能なオブジェクト
ゲーム内では個別に変化するステータス値を持つが、ccptsの仕様には含まないので、値オブジェクト
内部にいろいろな値オブジェクトを持つ集約ルート
  • 仕様
    • 自身を特定する「アルカナコード」を持つ
      • 「アルカナコード」が同じ<<アルカナ>>は等価である
    • 0以上の「コスト」を持つ

メンバー(Member):値オブジェクト

パーティーの枠にセットする単位
バトルで実際に操作する「メインアルカナ」と、追加効果のみが発動する「絆アルカナ」を組み合わせる
操作に必要な「メインアルカナ」は必ず存在する
  • 仕様
    • 「メインアルカナ」と「絆アルカナ」が存在する
    • 「メインアルカナ」は必須、「絆アルカナ」はなくても良い
    • 「コスト」は「メインアルカナ」と「絆アルカナ」の合計
      • 「絆アルカナ」が存在しなければ、「メインアルカナ」のみで計算
    • 自身を表す「コード」を持つ
      • 「メインメンバー」の「アルカナコード」+「絆アルカナ」の「アルカナコード」
      • 「絆アルカナ」が存在しなければ、「絆アルカナ」のコードは「N」

プレイヤーパーティー(PlayerParty):エンティティ

パーティーのうち、プレイヤーが自分で構成できるメンバーの集合
ゲーム内ではプレイヤーのレベルに応じて最大コストが設定されており、プレイヤーはパーティーのコストに関心がある
  • 仕様

    • 6つの「位置」を持つ
      • mem1, mem2, mem3, mem4, sub1, sub2
    • 各「位置」には<<メンバー>>を配置できる
    • 各「位置」は<<メンバー>>が空でも許容される
    • <<プレイヤーパーティー>>の中で同一の<<アルカナ>>は一つしか存在できない
    • 「コスト」は各<<メンバー>>の「コスト」を合計したもの
    • 自身を表す「コード」を持つ
      • 各「位置」の順に<<メンバー>>の「コード」を並べていく
      • <<メンバー>>がいないところは「NN」とする
  • 操作

    • 「位置」と「メインアルカナ」と「絆アルカナ」を指定して、メンバーを登録する
    • 「位置」と<<メンバー>>を指定して登録する
    • 「位置」を指定して<<メンバー>>を削除する
    • 「位置」を指定して「絆アルカナ」を削除する

フレンドメンバー(FriendMember):値オブジェクト

フレンドが他者に貸すためにセットされた<<メンバー>>
バトルに参加する際、一つ選択する
  • 仕様
    • <<メンバー>>を一つ持つことができる
    • <<メンバー>>が空も許される
    • 「コスト」は<<メンバー>>の状態にかかわらず、常に0である
    • <<プレイヤーパーティー>>に存在する<<アルカナ>>も保持できる
    • 自身を表すコードを持つ
      • メンバーを持つならメンバーのコード、ないなら「NN」

パーティー(Party):エンティティ・集約ルート

バトルに参加するために構成される、<<メンバー>>の集合
<<プレイヤーパーティー>>と<<フレンドメンバー>>で構成される
  • 仕様

    • 「コスト」は<<プレイヤーパーティー>>の「コスト」
    • ユニークな「コード」を持つ
      • <<プレイヤーパーティー>>のコード+<<フレンドメンバー>>のコード
  • 操作

    • 「位置」と<<メンバー>>を指定して登録する
    • 「位置」と「メインアルカナ」と「絆アルカナ」を指定して、<<メンバー>>を登録する
    • 「位置」を指定して<<メンバー>>を削除する
    • 「位置」を指定して「絆アルカナ」を削除する
    • <<メンバー>>を<<フレンドメンバー>>として登録する
    • 「メインアルカナ」と「絆アルカナ」を渡してフレンドを登録する
    • <<フレンドメンバー>>を削除する
    • <<フレンドメンバー>>の「絆アルカナ」を削除する
// 値オブジェクト:アルカナ
export default class Arcana {
private _arcanaCode: string
private _cost: number
constructor(code: string, cost: number) {
this._arcanaCode = code
if (this.cost >= 0) {
this._cost = cost
} else {
this._cost = 0
}
}
get arcanaCode(): string {
return this._arcanaCode
}
get cost(): number {
return this._cost
}
// 等価性判定
public isEqual(other: Arcana | null) {
if (!other) {
return false
}
if (this._arcanaCode !== other.arcanaCode) {
return false
}
return true
}
}
import Member from "./Member"
// 値オブジェクト:フレンドメンバー
export default class FriendMember {
private _member: Member | null
constructor(mem: Member | null) {
this._member = mem
}
get member(): Member | null {
return this._member
}
// フレンドのコストは常に0
get cost(): number {
return 0
}
get code(): string {
if (!this._member) {
return "NN"
}
return this._member.code
}
}
import Arcana from "./Arcana"
// 値オブジェクト:メンバー
export default class Member {
private _mainArcana: Arcana
private _chainArcana: Arcana | null
// NOTE: 絆アルカナの追加は新しいメンバーの生成
constructor(main: Arcana, chain: Arcana | null) {
this._mainArcana = main
this._chainArcana = chain
}
get cost(): number {
let cost = this._mainArcana.cost
if (this._chainArcana) {
cost += this._chainArcana.cost
}
return cost
}
get mainArcana(): Arcana {
return this._mainArcana
}
get chainArcana(): Arcana | null {
return this._chainArcana
}
get code(): string {
let c = this._mainArcana.arcanaCode
if (this._chainArcana) {
c += this._chainArcana.arcanaCode
} else {
c += "N"
}
return c
}
// メンバーが指定されたアルカナを保持しているか?
public hasArcana(target: Arcana | null): boolean {
if (this._mainArcana.isEqual(target)) {
return true
}
if (!this._chainArcana) {
return false
}
if (this._chainArcana.isEqual(target)) {
return true
}
return false
}
}
import * as _ from "lodash"
import Arcana from "./Arcana"
import Member from "./Member"
import PlayerParty from "./PlayerParty"
import FriendMember from "./FriendMember"
// エンティティ:パーティー
export default class Party {
public static FRIEND_POSITION = "friend"
public static POSITIONS: string[] = _.flatten([PlayerParty.POSITIONS, Party.FRIEND_POSITION])
private _playerParty: PlayerParty
private _friendMember: FriendMember
constructor() {
this._playerParty = new PlayerParty()
this._friendMember = new FriendMember(null)
}
get cost() {
return this._playerParty.cost
}
get code() {
return `${this._playerParty.code}${this._friendMember.code}`
}
// 位置に対応するメンバー取得
public memberFor(pos: string): Member | null {
if (this.isFriendPosition(pos)) {
return this._friendMember.member
}
return this._playerParty.memberFor(pos)
}
// メンバーをフレンドとして登録する
public addFriend(mem: Member) {
return this._friendMember = new FriendMember(mem)
}
// フレンドを削除する
public removeFriend() {
this._friendMember = new FriendMember(null)
}
// メインアルカナと絆アルカナを指定してフレンドを登録する
public createFriend(main: Arcana, chain: Arcana | null) {
this.addFriend(new Member(main, chain))
}
// フレンドの絆アルカナを削除
public removeFriendChainArcana() {
const mem = this._friendMember.member
if (!mem) {
return
}
this.addFriend(new Member(mem.mainArcana, null))
}
// 位置を指定してメンバーを登録
public addMember(pos: string, mem: Member) {
if (this.isFriendPosition(pos)) {
return this.addFriend(mem)
}
return this._playerParty.addMember(pos, mem)
}
// 位置とメインアルカナと絆アルカナを指定してメンバーを登録
public createMember(pos: string, main: Arcana, chain: Arcana | null) {
this.addMember(pos, new Member(main, chain))
}
// 位置を指定してメンバーを削除する
public removeMember(pos): void {
if (this.isFriendPosition(pos)) {
this.removeFriend()
}
this._playerParty.removeMember(pos)
}
// 位置を指定してメンバーの絆アルカナを削除する
public removeMemberChainArcana(pos: string): void {
const mem = this.memberFor(pos)
if (!mem) {
return
}
this.addMember(pos, new Member(mem.mainArcana, null))
}
private isFriendPosition(pos: string): boolean {
return (pos === Party.FRIEND_POSITION)
}
}
import * as _ from "lodash"
import Member from "./Member"
import Arcana from "./Arcana"
// エンティティ:プレイヤーパーティー
export default class PlayerParty {
// パーティーの位置リスト
public static POSITIONS: string[] = ["mem1", "mem2", "mem3", "mem4", "sub1", "sub2"]
private _members: { [key: string]: Member | null }
constructor() {
this._members = {}
}
get cost(): number {
return _.chain(this._members)
.flatMap((mem, _pos) => {
if (!mem) {
return 0
}
return mem.cost
})
.sum()
.value()
}
// プレイヤーパーティーのコード
get code(): string {
let c = ""
PlayerParty.POSITIONS.forEach((pos) => {
const m = this._members[pos]
if (m) {
c += m.code
} else {
c += "NN"
}
})
return c
}
// 位置に対応するメンバー取得
public memberFor(pos: string): Member | null {
if (!this.isValidPosition(pos)) {
return null
}
return this._members[pos]
}
// 位置にメンバーを追加する
public addMember(pos: string, mem: Member): void {
if (!this.isValidPosition(pos)) {
return
}
// 重複するアルカナを削除する
this.removeDuplicateMember(mem)
// メンバーをセット
this._members[pos] = mem
}
// 位置にメインアルカナと絆アルカナを指定してメンバーを追加する
public createMember(pos: string, main: Arcana, chain: Arcana | null): void {
this.addMember(pos, new Member(main, chain))
}
// 位置を指定してメンバーを削除する
public removeMember(pos: string): void {
this._members[pos] = null
}
// 位置を指定してメンバーの絆アルカナを削除する
public removeMemberChainArcana(pos: string): void {
const mem = this.memberFor(pos)
if (!mem) {
return
}
this.addMember(pos, new Member(mem.mainArcana, null))
}
private isValidPosition(pos: string): boolean {
return PlayerParty.POSITIONS.includes(pos)
}
// メンバーに含まれるアルカナが重複していたら削除
private removeDuplicateMember(mem: Member): void {
PlayerParty.POSITIONS.forEach((pos) => {
const m = this._members[pos]
if (!m) {
return
}
if (m.hasArcana(mem.mainArcana)) {
this.removeDuplicateArcana(pos, mem.mainArcana)
}
if (mem.chainArcana && m.hasArcana(mem.chainArcana)) {
this.removeDuplicateArcana(pos, mem.chainArcana)
}
})
}
// 対象のメンバーから指定されたアルカナを削除
private removeDuplicateArcana(pos: string, target: Arcana): void {
const org = this._members[pos]
if (!org) {
return
}
if (!org.hasArcana(target)) {
return
}
// メインアルカナが同じの場合、メンバーごと削除
if (org.mainArcana.isEqual(target)) {
this._members[pos] = null
}
// 絆アルカナが同じの場合、絆アルカナだけ削除
if (org.chainArcana && org.chainArcana.isEqual(target)) {
this._members[pos] = new Member(org.mainArcana, null)
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment