;
19 |
20 | export type Handler = (
21 | state: S,
22 | payload: PayloadAction["payload"]
23 | ) => EntityContainer | void | undefined;
24 |
25 | export type CharacterHandler = Handler;
26 | export type PlayerHandler = Handler;
27 | export type HeroHandler = Handler;
28 | export type MinionHandler = Handler;
29 | export type HeroPowerHandler = Handler;
30 | export type WeaponHandler = Handler;
31 |
32 | export const extractEntity = <
33 | E extends Entity = Entity,
34 | P extends EntityPayload = EntityPayload
35 | >(
36 | handler: Handler
37 | ): CaseReducer> => (state, action) =>
38 | handler(state[action.payload.id] as E, action.payload);
39 |
--------------------------------------------------------------------------------
/src/UI/App.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { DndProvider } from "react-dnd";
3 | import { HTML5Backend } from "react-dnd-html5-backend";
4 | import { TouchBackend } from "react-dnd-touch-backend";
5 | import {
6 | Container,
7 | Grid,
8 | Header,
9 | GridRow,
10 | GridColumn
11 | } from "semantic-ui-react";
12 | import TargetableBattlefield from "./Play/Battlefield";
13 | import Toastr from "toastr";
14 | import "toastr/build/toastr.css";
15 | import { isTouchDevice } from "./utils";
16 |
17 | Toastr.options.timeOut = 3000;
18 |
19 | const DnDBackend = isTouchDevice() ? TouchBackend : HTML5Backend;
20 |
21 | const App: React.FC = (props) => (
22 |
23 |
24 |
25 |
26 |
27 |
32 | Hearthstone simulator written using React & Redux.
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | );
44 |
45 | export default App;
46 |
--------------------------------------------------------------------------------
/src/redux/modules/play/weaponReducer.ts:
--------------------------------------------------------------------------------
1 | import { createReducer, PayloadAction } from "@reduxjs/toolkit";
2 | import {
3 | Character,
4 | EntityContainer,
5 | Weapon,
6 | isMinion,
7 | getWeapon,
8 | getEntity
9 | } from "../../../models";
10 | import {
11 | attackCharacter,
12 | destroyWeapon,
13 | equipWeapon,
14 | EquipWeaponPayload
15 | } from "./actions";
16 | import { EntityPayloadAction } from "../../utils";
17 |
18 | const equipWeaponHandler = (
19 | state: EntityContainer,
20 | action: PayloadAction
21 | ) => {
22 | state[action.payload.id] = action.payload.weapon;
23 | };
24 |
25 | const destroyWeaponHandler = (
26 | state: EntityContainer,
27 | action: EntityPayloadAction
28 | ) => {
29 | delete state[action.payload.id];
30 | };
31 |
32 | const attackCharacterHandler = (
33 | state: EntityContainer,
34 | { payload }: EntityPayloadAction
35 | ) => {
36 | const attacker = state[payload.id] as Character;
37 |
38 | if (isMinion(attacker)) return;
39 |
40 | if (attacker.weaponID) {
41 | const weapon = getEntity(state, attacker.weaponID) as Weapon;
42 | weapon.health--;
43 | }
44 | };
45 |
46 | export default createReducer(
47 | {},
48 | {
49 | [destroyWeapon.type]: destroyWeaponHandler,
50 | [equipWeapon.type]: equipWeaponHandler,
51 | [attackCharacter.type]: attackCharacterHandler
52 | }
53 | );
54 |
--------------------------------------------------------------------------------
/src/UI/components/Card.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Label, List, Popup, Segment } from "semantic-ui-react";
3 | import { AbilityList, CardArt } from "./index";
4 | import { Card as ICard, isMinion, isWeapon } from "../../models";
5 |
6 | interface CardProps {
7 | active?: boolean;
8 | card: ICard;
9 | }
10 |
11 | const Card: React.FC = ({ card, active = true }) => {
12 | const { attack, cardID, cost, name, text } = card;
13 |
14 | return (
15 |
18 | {name}
19 |
20 |
21 |
22 |
23 |
26 |
27 |
30 |
34 |
35 |
36 | }
37 | content={
38 |
39 |
{name}
40 |
{text}
41 |
42 |
43 | }
44 | />
45 | );
46 | };
47 |
48 | export default Card;
49 |
--------------------------------------------------------------------------------
/src/models/containers/EntityContainer.ts:
--------------------------------------------------------------------------------
1 | import _ from "lodash/fp";
2 | import { Container } from "../Container";
3 | import { Entity } from "../Entity";
4 |
5 | export type EntityContainer = Container;
6 |
7 | export const makeEntityContainer = (entities: T[]) =>
8 | _.indexBy(_.prop("id"), entities) as EntityContainer;
9 |
10 | // FIXME
11 | // type IGetEntityOverload = {
12 | // (id: number, container: Container): T;
13 | // (id: null, container: Container): null;
14 | // }
15 | // export const getEntity:IGetEntityOverload = (id: number|null, container: Container) => !!id ? container[id] : null;
16 |
17 | export function getEntity(
18 | container: Container,
19 | id: number
20 | ): T;
21 | export function getEntity(
22 | container: Container,
23 | id: null
24 | ): null;
25 | export function getEntity(
26 | container: Container,
27 | id: number | null
28 | ): T | null;
29 | export function getEntity(
30 | container: Container,
31 | id: number | null
32 | ): T | null {
33 | if (!id) return null;
34 | return container[id];
35 | }
36 |
37 | // export function getEntity(id: P, container:Container): (P extends null ? null : T) {
38 | // if (!id) return null;
39 | // return container[id];
40 | // }
41 | // export const getEntitiesById = (container: T, ids: number[]) =>
42 | // _.pick(ids, container) as T;
43 |
--------------------------------------------------------------------------------
/src/UI/components/Minion.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Label, Popup, Segment, Transition } from "semantic-ui-react";
3 | import Sleep from "./Sleep";
4 | import CardArt from "./CardArt";
5 | import { Minion as IMinion } from "../../models";
6 | import AbilityList from "./AbilityList";
7 |
8 | interface MinionProps extends IMinion {
9 | active: boolean;
10 | isOver: boolean;
11 | }
12 |
13 | const Minion: React.FC = ({
14 | active,
15 | abilities,
16 | attack,
17 | cardID,
18 | exhausted,
19 | isOver,
20 | health,
21 | maxHealth,
22 | name,
23 | text
24 | }) => (
25 |
30 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
42 |
50 |
51 |
52 |
53 |
54 | }
55 | />
56 | );
57 |
58 | export default Minion;
59 |
--------------------------------------------------------------------------------
/src/redux/modules/play/heroReducer.ts:
--------------------------------------------------------------------------------
1 | import { createReducer, PayloadAction } from "@reduxjs/toolkit";
2 | import {
3 | calculateFatigueDmg,
4 | EntityContainer,
5 | Hero,
6 | Player,
7 | shouldBeDestroyed,
8 | Weapon
9 | } from "../../../models";
10 | import {
11 | destroyWeapon,
12 | equipWeapon,
13 | EquipWeaponPayload,
14 | fatigueDamage,
15 | FatigueDamagePayload
16 | } from "./actions";
17 | import { extractEntity } from "../../utils";
18 |
19 | const destroyWeaponHandler = (
20 | state: EntityContainer,
21 | { payload: { heroId, attack } }: PayloadAction
22 | ) => {
23 | const hero = state[heroId] as Hero;
24 | hero.weaponID = null;
25 | hero.attack = Math.min(hero.attack - attack, 0);
26 | };
27 |
28 | const equipWeaponHandler = (
29 | state: EntityContainer,
30 | { payload: { weapon } }: PayloadAction
31 | ) => {
32 | const hero = state[weapon.heroId] as Hero;
33 | hero.attack += weapon.attack;
34 | hero.weaponID = weapon.id;
35 | };
36 |
37 | // TODO: use simple DEAL_DAMAGE action?
38 | const fatigueDamageHandler = (
39 | state: EntityContainer,
40 | { payload }: PayloadAction
41 | ) => {
42 | const hero = state[payload.heroId] as Hero;
43 | const player = state[payload.id] as Player;
44 |
45 | hero.health -= calculateFatigueDmg(player);
46 | hero.destroyed = shouldBeDestroyed(hero);
47 | };
48 |
49 | export default createReducer(
50 | {},
51 | {
52 | [destroyWeapon.type]: destroyWeaponHandler,
53 | [equipWeapon.type]: equipWeaponHandler,
54 | [fatigueDamage.type]: fatigueDamageHandler
55 | }
56 | );
57 |
--------------------------------------------------------------------------------
/src/models/containers/MinionContainer.ts:
--------------------------------------------------------------------------------
1 | import _ from "lodash/fp";
2 | import {
3 | CharacterContainer,
4 | EntityContainer,
5 | makeEntityContainer
6 | } from "./index";
7 | import { CardType, Controller } from "../enums";
8 | import { Game } from "../Game";
9 | import { craftMinion, CraftMinionProps, Minion } from "../Minion";
10 | import { hasTaunt } from "../Card";
11 | import { Character } from "../Character";
12 | import { Container } from "../Container";
13 |
14 | export type MinionContainer = Container;
15 |
16 | export const ownerMinions = _.curry(
17 | (controller: Controller, container: EntityContainer) =>
18 | _.pickBy(
19 | _.whereEq({ owner: controller, type: CardType.Minion }),
20 | container
21 | ) as MinionContainer
22 | );
23 | export const playerMinions = (game: Game) =>
24 | ownerMinions(game.state.playerID, game.play);
25 | export const opponentMinions = (game: Game) =>
26 | ownerMinions(game.state.opponentID, game.play);
27 |
28 | export const craftMinions = (...props: CraftMinionProps[]) =>
29 | _.map(craftMinion, props) as Minion[];
30 | export const craftMinionContainer = (...props: CraftMinionProps[]) =>
31 | makeEntityContainer(craftMinions(...props)) as MinionContainer;
32 |
33 | export const minionsFromContainer = (entities: EntityContainer) =>
34 | _.pickBy(_.whereEq({ type: CardType.Minion }), entities) as MinionContainer;
35 |
36 | export const anyTaunts = (minions: CharacterContainer) =>
37 | _.any(hasTaunt, minions);
38 |
39 | export const isValidTarget = (
40 | target: Character,
41 | characters: CharacterContainer
42 | ) => (anyTaunts(characters) ? hasTaunt(target) : true);
43 |
--------------------------------------------------------------------------------
/src/UI/components/Hero.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Grid, Header, Segment, Statistic } from "semantic-ui-react";
3 | import { Hero as IHero, Weapon as IWeapon } from "../../models";
4 | import CardArt from "./CardArt";
5 | import Weapon from "./Weapon";
6 |
7 | interface HeroProps extends IHero {
8 | active: boolean;
9 | isOver: boolean;
10 | weapon: IWeapon | null;
11 | mana: number;
12 | maximumMana: number;
13 | }
14 |
15 | const Hero: React.FC = ({
16 | active,
17 | exhausted,
18 | isOver,
19 | name,
20 | cardID,
21 | armor,
22 | health,
23 | mana,
24 | maximumMana,
25 | weapon
26 | }) => (
27 |
28 |
29 |
30 | {weapon && }
31 |
32 |
33 |
38 |
39 |
40 |
41 |
42 | 0 ? 3 : 2}>
43 | {armor > 0 && }
44 |
45 |
46 |
52 |
53 |
54 |
55 |
56 | );
57 |
58 | export default Hero;
59 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "typescript-redux-card-game",
3 | "version": "0.4.0",
4 | "private": true,
5 | "homepage": "https://zergetaev.ru/typescript-redux-card-game/",
6 | "engines": {
7 | "node": ">=14.0.0"
8 | },
9 | "dependencies": {
10 | "@reduxjs/toolkit": "^1.4.0",
11 | "classnames": "^2.2.6",
12 | "hearthstone-parser": "^4.3.1",
13 | "hearthstonejson-client": "https://github.com/zernie/npm-hearthstonejson-client.git#build",
14 | "lodash": "^4.17.15",
15 | "prettier": "^2.0.5",
16 | "react": "^16.13.1",
17 | "react-dnd": "^11.1.3",
18 | "react-dnd-html5-backend": "^11.1.3",
19 | "react-dnd-touch-backend": "^11.1.3",
20 | "react-dom": "^16.13.1",
21 | "react-hs-components": "https://libs.hearthsim.net/react-hs-components-v0.3.0.tgz",
22 | "react-redux": "^7.2.0",
23 | "react-scripts": "^3.4.1",
24 | "reduce-reducers": "^1.0.4",
25 | "redux-orm": "^0.16.2",
26 | "semantic-ui-react": "^0.88.2",
27 | "sunwell": "^0.10.0",
28 | "toastr": "^2.1.4"
29 | },
30 | "scripts": {
31 | "start": "react-scripts start",
32 | "build": "react-scripts build",
33 | "test": "react-scripts test --env=jsdom",
34 | "eject": "react-scripts eject",
35 | "predeploy": "npm run build",
36 | "deploy": "gh-pages -d build"
37 | },
38 | "devDependencies": {
39 | "@types/classnames": "^2.2.10",
40 | "@types/jest": "^26.0.3",
41 | "@types/lodash": "^4.14.157",
42 | "@types/node": "^14.0.14",
43 | "@types/react": "^16.9.41",
44 | "@types/react-dom": "^16.9.8",
45 | "@types/react-redux": "^7.1.9",
46 | "@types/redux-orm": "^0.13.6",
47 | "@types/toastr": "^2.1.38",
48 | "node-gyp": "^7.0.0",
49 | "typescript": "~3.9.6"
50 | },
51 | "resolutions": {
52 | "@types/prop-types": "15.7.3"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/redux/modules/play/actions.ts:
--------------------------------------------------------------------------------
1 | import { createAction } from "@reduxjs/toolkit";
2 | import { Weapon, Minion, TriggerType, Character } from "../../../models";
3 | import { EntityPayload } from "../../utils";
4 |
5 | export type GainManaPayload = EntityPayload<{
6 | amount?: number;
7 | }>;
8 | export type SpendManaPayload = EntityPayload<{
9 | amount: number;
10 | }>;
11 | export type EquipWeaponPayload = EntityPayload<{
12 | weapon: Weapon;
13 | }>;
14 |
15 | // export type DealDamagePayload = EntityPayload<{ amount: number }>;
16 | export interface DealDamagePayload {
17 | amount: number;
18 | id: number | number[];
19 | }
20 |
21 | export type FatigueDamagePayload = EntityPayload<{ heroId: number }>;
22 | export type TriggerEventPayload = {
23 | trigger: TriggerType;
24 | id: number | number[];
25 | };
26 | export interface SourceTargetPayload {
27 | source: Character;
28 | target: Character;
29 | }
30 |
31 | export const attackCharacter = createAction("ATTACK_CHARACTER");
32 | export const dealDamage = createAction("DEAL_DAMAGE");
33 | export const exhaust = createAction("EXHAUST");
34 | export const destroyWeapon = createAction("DESTROY_WEAPON");
35 | export const equipWeapon = createAction("EQUIP_WEAPON");
36 | export const gainMana = createAction("GAIN_MANA");
37 | export const restoreMana = createAction("RESTORE_MANA");
38 | export const spendMana = createAction("SPEND_MANA");
39 | export const summonMinion = createAction("SUMMON_MINION");
40 | export const processDeaths = createAction("PROCESS_DEATHS");
41 | export const triggerEvent = createAction("TRIGGER_EVENT");
42 | export const fatigueDamage = createAction(
43 | "FATIGUE_DAMAGE"
44 | );
45 |
--------------------------------------------------------------------------------
/src/models/utils.ts:
--------------------------------------------------------------------------------
1 | // import _ from "lodash/fp";
2 | import { ThunkAction } from "redux-thunk";
3 | import { Game } from "./Game";
4 | import { Action } from "redux";
5 | import { CardData, CardType, Controller, Zone } from "./enums";
6 | import { craftMinion } from "./Minion";
7 | import { craftWeapon } from "./Weapon";
8 | import { Card } from "./Card";
9 | import { craftHero } from "./Hero";
10 |
11 | let _lastId = 0;
12 | export const newId = (): number => new Date().getTime() + _lastId++;
13 |
14 | export type AppThunk = ThunkAction>;
15 |
16 | /** Import a card from hearthstonejson.com */
17 | export function importCard(
18 | data: CardData,
19 | zone: Zone,
20 | owner: Controller
21 | ): Card {
22 | switch (data.type) {
23 | case CardType.Hero:
24 | return craftHero({
25 | cardID: data.id,
26 | health: data.health || 0,
27 | abilities: data.mechanics || [],
28 | attack: data.attack || 0,
29 | name: data.name,
30 | zone,
31 | owner
32 | });
33 | case CardType.Minion:
34 | return craftMinion({
35 | cardID: data.id,
36 | maxHealth: data.health || 0,
37 | abilities: data.mechanics || [],
38 | attack: data.attack || 0,
39 | cost: data.cost || 0,
40 | name: data.name,
41 | zone,
42 | owner
43 | });
44 | case CardType.Weapon:
45 | return craftWeapon({
46 | cardID: data.id,
47 | health: data.health || 0,
48 | abilities: data.mechanics || [],
49 | attack: data.attack || 0,
50 | heroId: 0, // FIXME
51 | cost: data.cost || 0,
52 | name: data.name,
53 | zone,
54 | owner
55 | });
56 | default:
57 | throw new Error(`Import of CardType "${data.type}" is not implemented yet.`);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
23 | React/Redux card game
24 |
25 |
26 |
29 |
30 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/UI/Play/DnDMinion.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useDispatch } from "react-redux";
3 | import { useDrag, useDrop } from "react-dnd";
4 | import {
5 | canAttack,
6 | CardType,
7 | Character,
8 | isValidTarget,
9 | Minion as IMinion,
10 | MinionContainer,
11 | minionsFromContainer,
12 | ownerMinions
13 | } from "../../models";
14 | import { performAttack } from "../../redux/modules/play/characterReducer";
15 | import { useGame } from "../hooks";
16 | import { Minion } from "../components/";
17 |
18 | export interface MinionProps {
19 | character: IMinion;
20 | }
21 |
22 | /**
23 | * Drag & Drop Minion component
24 | */
25 | const DnDMinion: React.FC = ({ character }) => {
26 | const dispatch = useDispatch();
27 | const { play, state } = useGame();
28 | const [{ isOver, canDrop }, dropRef] = useDrop({
29 | accept: [CardType.Minion, CardType.Hero],
30 | drop: (source: Character, monitor) =>
31 | dispatch(
32 | performAttack({
33 | source,
34 | target: character
35 | })
36 | ),
37 | canDrop: (target: IMinion) => {
38 | const enemyMinions = ownerMinions(
39 | character.owner,
40 | minionsFromContainer(play)
41 | ) as MinionContainer;
42 |
43 | return (
44 | target.owner !== character.owner &&
45 | isValidTarget(character, enemyMinions)
46 | );
47 | },
48 | collect: (monitor) => ({
49 | isOver: monitor.isOver(),
50 | canDrop: monitor.canDrop()
51 | })
52 | });
53 | const [{ canDrag }, dragRef] = useDrag({
54 | item: character,
55 | canDrag: (monitor) =>
56 | character.owner === state.activePlayer && canAttack(character),
57 | collect: (monitor) => ({
58 | canDrag: monitor.canDrag()
59 | })
60 | });
61 |
62 | return (
63 |
68 | );
69 | };
70 |
71 | export default DnDMinion;
72 |
--------------------------------------------------------------------------------
/src/redux/modules/handReducer.ts:
--------------------------------------------------------------------------------
1 | // import _ from "lodash/fp";
2 | import { createAction, createReducer, PayloadAction } from "@reduxjs/toolkit";
3 | import {
4 | AppThunk,
5 | Card,
6 | CardContainer,
7 | canSpendMana,
8 | getActivePlayer,
9 | CardType,
10 | Zone,
11 | hasCharge
12 | } from "../../models";
13 | import { equipWeapon, spendMana, summonMinion } from "./play/actions";
14 | import { drawCard } from "./deckReducer";
15 |
16 | export const playCard = createAction("PLAY_CARD");
17 |
18 | export const playerUseCard = (payload: Card): AppThunk => (
19 | dispatch,
20 | getState
21 | ) => {
22 | const state = getState();
23 | // const heroID = activeHero(state);
24 | const player = getActivePlayer(state);
25 | if (!canSpendMana(getActivePlayer(state), payload.cost)) {
26 | return console.warn("Not enough mana!");
27 | }
28 |
29 | dispatch(playCard(payload));
30 | dispatch(spendMana({ amount: payload.cost, id: player.id }));
31 |
32 | switch (payload.type) {
33 | case CardType.Minion:
34 | const minion = {
35 | ...payload,
36 | zone: Zone.Play,
37 | exhausted: !hasCharge(payload)
38 | };
39 | dispatch(summonMinion(minion));
40 | break;
41 | case CardType.Weapon:
42 | dispatch(equipWeapon({ id: payload.id, weapon: payload }));
43 | return;
44 | default:
45 | alert(`Unknown card type: ${payload.type}`);
46 | throw new Error("Not implemented yet!");
47 | }
48 | };
49 |
50 | export const playCardHandler = (
51 | state: CardContainer,
52 | action: PayloadAction
53 | ) => {
54 | // state[action.payload.id].zone = Zone.Graveyard;
55 | delete state[action.payload.id];
56 | };
57 |
58 | const drawCardHandler = (state: CardContainer, action: PayloadAction) => {
59 | const card = { ...action.payload, zone: Zone.Hand } as Card;
60 | state[action.payload.id] = card;
61 | };
62 |
63 | export default createReducer(
64 | {},
65 | {
66 | [playCard.type]: playCardHandler,
67 | [drawCard.type]: drawCardHandler
68 | }
69 | );
70 |
--------------------------------------------------------------------------------
/src/models/Hero.ts:
--------------------------------------------------------------------------------
1 | import _ from "lodash/fp";
2 | import { Character, ICharacter } from "./Character";
3 | import { Abilities } from "./Abilities";
4 | import { newId } from "./utils";
5 | import { Game } from "./Game";
6 | import { CardClass, CardType, Controller, Zone } from "./enums";
7 | import { isHero } from "./Entity";
8 | import { MAX_HEALTH } from "./constants";
9 | // import { Model } from "redux-orm";
10 |
11 | export interface Hero extends ICharacter {
12 | readonly type: CardType.Hero;
13 | weaponID: number | null;
14 | }
15 |
16 | interface CraftHeroProps {
17 | cardID: string;
18 | name: string;
19 | owner: Controller;
20 |
21 | cardClass?: CardClass;
22 | abilities?: Abilities;
23 | armor?: number;
24 | attack?: number;
25 | attacksPerformed?: number;
26 | exhausted?: boolean;
27 | health?: number;
28 | maxHealth?: number;
29 | weaponId?: number;
30 | zone?: Zone;
31 | }
32 |
33 | export const craftHero = (props: CraftHeroProps): Hero =>
34 | ({
35 | abilities: [],
36 | armor: 0,
37 | attack: 0,
38 | attacksPerformed: 0,
39 | attacking: false,
40 | defending: false,
41 | cost: 0,
42 | destroyed: false,
43 | exhausted: false,
44 | health: props.maxHealth || MAX_HEALTH,
45 | id: newId(),
46 | maxHealth: 30,
47 | zone: Zone.Play,
48 | cardClass: CardClass.Neutral,
49 | weaponID: null,
50 | text: null,
51 | ...props,
52 | type: CardType.Hero
53 | } as Hero);
54 |
55 | export const reduceArmor = (hero: Hero, damage: number): number =>
56 | Math.max(0, hero.armor - damage);
57 |
58 | export const reduceHealth = (character: Character, damage: number): number =>
59 | Math.min(
60 | character.health,
61 | isHero(character)
62 | ? character.health + character.armor - damage
63 | : character.health - damage
64 | );
65 |
66 | export const getPlayerHero = (game: Game): Hero =>
67 | game.play[game.state.playerHeroID] as Hero;
68 |
69 | export const getOpponentHero = (game: Game): Hero =>
70 | game.play[game.state.opponentHeroID] as Hero;
71 |
72 | // export class Hero extends Model {
73 | // static modelName = "Hero";
74 | // }
75 |
--------------------------------------------------------------------------------
/src/UI/Play/DnDHero.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useDispatch } from "react-redux";
3 | import { useDrag, useDrop } from "react-dnd";
4 | import {
5 | Hero as IHero,
6 | CardType,
7 | Player,
8 | Character,
9 | getWeapon,
10 | canAttack,
11 | isValidTarget,
12 | minionsFromContainer,
13 | ownerMinions,
14 | Weapon
15 | } from "../../models";
16 | import { performAttack } from "../../redux/modules/play/characterReducer";
17 | import { useGame } from "../hooks";
18 | import { Hero } from "../components";
19 |
20 | interface DnDHeroProps {
21 | active: boolean;
22 | hero: IHero;
23 | player: Player;
24 | }
25 |
26 | /**
27 | * Drag & Drop Hero component
28 | */
29 | const DnDHero: React.FC = ({ active, hero, player }) => {
30 | const game = useGame();
31 | const dispatch = useDispatch();
32 | const {
33 | state: { activePlayer }
34 | } = game;
35 | const { mana, maximumMana } = player;
36 | const { weaponID } = hero;
37 | const [collectedProps, drag] = useDrag({
38 | item: hero,
39 | canDrag: (monitor) => hero.owner === activePlayer && canAttack(hero)
40 | });
41 |
42 | const [{ isOver }, drop] = useDrop({
43 | accept: [CardType.Minion, CardType.Hero],
44 | drop: (char: Character) =>
45 | dispatch(
46 | performAttack({
47 | source: char,
48 | target: hero
49 | })
50 | ),
51 | canDrop: (item: Character, monitor) => {
52 | const enemyMinions = ownerMinions(
53 | hero.owner,
54 | minionsFromContainer(game.play)
55 | );
56 |
57 | return item.owner !== player.id && isValidTarget(item, enemyMinions);
58 | },
59 | collect: (monitor) => ({
60 | isOver: monitor.isOver()
61 | })
62 | });
63 | const weapon = getWeapon(game.play, weaponID) as Weapon;
64 |
65 | return (
66 |
78 | );
79 | };
80 |
81 | export default DnDHero;
82 |
--------------------------------------------------------------------------------
/src/UI/hooks.ts:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import {
3 | Controller,
4 | Game,
5 | getOpponent,
6 | getOpponentHero,
7 | getPlayer,
8 | getPlayerHero,
9 | isGameOver,
10 | minionsFromContainer,
11 | opponentDeck,
12 | opponentHand,
13 | opponentMinions,
14 | ownerMinions,
15 | playerDeck,
16 | playerHand,
17 | playerMinions
18 | } from "../models";
19 |
20 | export const useGame = () => useSelector((game: Game) => game);
21 | export const useGameState = () => useSelector((game: Game) => game.state);
22 | // export const useActiveHand = () => useSelector(state => state.hand) as EntityContainer;
23 |
24 | export const useIsPlayerActive = () =>
25 | useSelector((game: Game) => game.state.activePlayer === game.state.playerID);
26 | export const useIsOpponentActive = () =>
27 | useSelector(
28 | (game: Game) => game.state.activePlayer === game.state.opponentID
29 | );
30 |
31 | export const usePlayer = () => useSelector((game: Game) => getPlayer(game));
32 | export const useOpponent = () => useSelector((game: Game) => getOpponent(game));
33 |
34 | export const usePlayerHero = () =>
35 | useSelector((game: Game) => getPlayerHero(game));
36 | export const useOpponentHero = () =>
37 | useSelector((game: Game) => getOpponentHero(game));
38 |
39 | export const usePlayMinions = () =>
40 | useSelector((game: Game) => minionsFromContainer(game.play));
41 | export const usePlayerMinions = () =>
42 | useSelector((game: Game) => playerMinions(game));
43 | export const useOpponentMinions = () =>
44 | useSelector((game: Game) => opponentMinions(game));
45 | export const useOwnerMinions = (owner: Controller) =>
46 | useSelector((game: Game) => ownerMinions(owner, game.play));
47 |
48 | export const usePlayerHand = () =>
49 | useSelector((game: Game) => playerHand(game));
50 | export const useOpponentHand = () =>
51 | useSelector((game: Game) => opponentHand(game));
52 |
53 | export const usePlayerDeck = () =>
54 | useSelector((game: Game) => playerDeck(game));
55 | export const useOpponentDeck = () =>
56 | useSelector((game: Game) => opponentDeck(game));
57 |
58 | export const useIsGameOver = () =>
59 | useSelector((game: Game) => isGameOver(game.state));
60 |
--------------------------------------------------------------------------------
/src/redux/modules/play/characterReducer.test.ts:
--------------------------------------------------------------------------------
1 | import characterReducer from "./characterReducer";
2 | import {
3 | EntityContainer,
4 | makeEntityContainer
5 | } from "../../../models/containers";
6 | import { attackCharacter, dealDamage, exhaust } from "./actions";
7 | import { Hero, importCard, Zone } from "../../../models";
8 | import { cards } from "../../../cards";
9 | import { nextTurn } from "../gameStateReducer";
10 |
11 | describe("characterReducer", () => {
12 | it("should handle EXHAUST", () => {
13 | const minion = importCard(cards["CS2_173"], Zone.Play, 0);
14 | const state: EntityContainer = makeEntityContainer([minion]);
15 |
16 | const result = characterReducer(state, exhaust({ id: minion.id }));
17 | expect(result[minion.id].exhausted).toBeTruthy();
18 | });
19 |
20 | it("should handle ATTACK_CHARACTER", () => {
21 | const minion = importCard(cards["CS2_173"], Zone.Play, 0);
22 | const state: EntityContainer = makeEntityContainer([minion]);
23 |
24 | const result = characterReducer(state, attackCharacter({ id: minion.id }));
25 | expect(result[minion.id].attacksPerformed).toEqual(1);
26 | });
27 |
28 | it("should handle DEAL_DAMAGE", () => {
29 | const minion = importCard(cards["CS2_173"], Zone.Play, 0);
30 | const hero = importCard(cards["HERO_01"], Zone.Play, 0) as Hero;
31 | hero.armor = 1;
32 | const state: EntityContainer = makeEntityContainer([minion, hero]);
33 |
34 | const result = characterReducer(
35 | state,
36 | dealDamage({ amount: 5, id: [minion.id, hero.id] })
37 | );
38 | expect(result[minion.id].health).toEqual(-4);
39 | expect(result[minion.id].destroyed).toBeTruthy();
40 |
41 | expect(result[hero.id].health).toEqual(26);
42 | expect(result[hero.id].destroyed).toBeFalsy();
43 | });
44 |
45 | it("should handle NEXT_TURN", () => {
46 | const minion = importCard(cards["CS2_173"], Zone.Play, 0);
47 | minion.attacksPerformed = 1;
48 | minion.exhausted = true;
49 | const state: EntityContainer = makeEntityContainer([minion]);
50 |
51 | const result = characterReducer(state, nextTurn());
52 | expect(result[minion.id].exhausted).toBeFalsy();
53 | expect(result[minion.id].attacksPerformed).toEqual(0);
54 | });
55 | });
56 |
--------------------------------------------------------------------------------
/src/redux/modules/play/playerReducer.ts:
--------------------------------------------------------------------------------
1 | import { createReducer } from "@reduxjs/toolkit";
2 | import _ from "lodash/fp";
3 | import {
4 | fatigueDamage,
5 | FatigueDamagePayload,
6 | gainMana,
7 | GainManaPayload,
8 | processDeaths,
9 | restoreMana,
10 | spendMana,
11 | SpendManaPayload
12 | } from "./actions";
13 | import {
14 | AppThunk,
15 | canSpendMana,
16 | CardType,
17 | EntityContainer,
18 | Hero,
19 | MAX_MANA,
20 | Player,
21 | PlayState,
22 | Zone
23 | } from "../../../models";
24 | import { extractEntity, HeroHandler, PlayerHandler } from "../../utils";
25 |
26 | const gainManaHandler: PlayerHandler = (
27 | state,
28 | { amount = 1 }
29 | ) => {
30 | if (state.maximumMana >= MAX_MANA)
31 | return console.warn(`Cannot gain more than max mana amount (${MAX_MANA}).`);
32 |
33 | state.maximumMana += amount;
34 | };
35 |
36 | const restoreManaHandler: PlayerHandler = (state) => {
37 | state.mana = state.maximumMana;
38 | };
39 |
40 | const spendManaHandler: PlayerHandler = (
41 | state: Player,
42 | { amount }
43 | ) => {
44 | if (!canSpendMana(state, amount))
45 | return console.warn(
46 | `Cannot spend more than current mana amount (${state.mana}).`
47 | );
48 |
49 | state.mana -= amount;
50 | };
51 |
52 | // TODO: refactor
53 | const processDeathsHandler = (state: EntityContainer) => {
54 | const destroyedHeroes = _.filter(
55 | _.whereEq({ destroyed: true, type: CardType.Hero, zone: Zone.Play }),
56 | state
57 | ) as Hero[];
58 |
59 | _.forEach((hero: Hero) => {
60 | const player = state[hero.owner] as Player;
61 | if (hero.destroyed) {
62 | player.playState = PlayState.Lost;
63 | }
64 | }, destroyedHeroes);
65 | };
66 |
67 | const fatigueDamageHandler: PlayerHandler = (player) => {
68 | player.fatigue++;
69 | };
70 |
71 | export default createReducer(
72 | {},
73 | {
74 | [gainMana.type]: extractEntity(gainManaHandler),
75 | [restoreMana.type]: extractEntity(restoreManaHandler),
76 | [spendMana.type]: extractEntity(spendManaHandler),
77 | [processDeaths.type]: processDeathsHandler,
78 | [fatigueDamage.type]: extractEntity(fatigueDamageHandler)
79 | }
80 | );
81 |
--------------------------------------------------------------------------------
/src/models/Player.ts:
--------------------------------------------------------------------------------
1 | import { CardType, Controller, PlayState, Zone } from "./enums";
2 | import { Game, GameState } from "./Game";
3 |
4 | export interface Player {
5 | readonly id: number;
6 | fatigue: number;
7 | heroID: number | null;
8 | mana: number;
9 | readonly name: string;
10 | maximumMana: number;
11 | playState: PlayState;
12 | readonly type: CardType.Player;
13 | zone: Zone;
14 | }
15 |
16 | interface CraftPlayerProps {
17 | name?: string;
18 | mana?: number;
19 | heroID?: number;
20 | maximumMana?: number;
21 | zone?: Zone;
22 | }
23 |
24 | export const craftPlayer = (props: CraftPlayerProps = {}): Player =>
25 | ({
26 | id: 0,
27 | fatigue: 0,
28 | mana: 0,
29 | maximumMana: 0,
30 | name: "Player",
31 | playState: PlayState.Playing,
32 | zone: Zone.Play,
33 | ...props,
34 | type: CardType.Player
35 | } as Player);
36 |
37 | export const craftOpponent = (props: CraftPlayerProps = {}): Player =>
38 | ({
39 | id: 1,
40 | fatigue: 0,
41 | mana: 0,
42 | name: "Opponent",
43 | maximumMana: 0,
44 | playState: PlayState.Playing,
45 | zone: Zone.Play,
46 | ...props,
47 | type: CardType.Player
48 | } as Player);
49 |
50 | export const canSpendMana = (player: Player, amount: number): boolean =>
51 | player.mana - amount >= 0;
52 |
53 | export const otherId = (state: GameState): Controller =>
54 | state.activePlayer === state.playerID ? state.opponentID : state.playerID;
55 | // export const other = (playerID: Controller): Controller =>
56 | // playerID === Controller.Player ? Controller.Opponent : Controller.Player;
57 |
58 | export const getActivePlayer = (game: Game) =>
59 | game.play[game.state.activePlayer] as Player;
60 |
61 | export const getPlayer = (game: Game) =>
62 | game.play[game.state.playerID] as Player;
63 |
64 | export const getOpponent = (game: Game) =>
65 | game.play[game.state.opponentID] as Player;
66 |
67 | export const hasLost = (player: Player) => player.playState === PlayState.Lost;
68 | export const hasWon = (player: Player) => player.playState === PlayState.Won;
69 | export const isPlaying = (player: Player) =>
70 | player.playState === PlayState.Playing;
71 | export const isTied = (player: Player) => player.playState === PlayState.Tied;
72 |
73 | export const calculateFatigueDmg = (player: Player) => player.fatigue + 1;
74 |
--------------------------------------------------------------------------------
/src/redux/modules/gameStateReducer.ts:
--------------------------------------------------------------------------------
1 | import _ from "lodash/fp";
2 | import { createAction, createReducer } from "@reduxjs/toolkit";
3 | import {
4 | AppThunk,
5 | GameState,
6 | Step,
7 | TriggerType,
8 | getActivePlayer,
9 | getOpponent,
10 | getPlayer,
11 | hasLost,
12 | MAX_CARDS_IN_HAND,
13 | otherId,
14 | ownerCards
15 | } from "../../models";
16 | import { burnCard, drawCard } from "./deckReducer";
17 | import {
18 | fatigueDamage,
19 | gainMana,
20 | processDeaths,
21 | restoreMana,
22 | triggerEvent
23 | } from "./play/actions";
24 | import initialState from "./initialState";
25 | import Toastr from "toastr";
26 |
27 | export const finishGame = createAction("FINISH_GAME");
28 | export const nextTurn = createAction("NEXT_TURN");
29 |
30 | const finishGameHandler = (state: GameState) => {
31 | state.step = Step.FinalGameOver;
32 | };
33 |
34 | const nextTurnHandler = (state: GameState) => {
35 | state.turn++;
36 | state.activePlayer = otherId(state);
37 | };
38 |
39 | export const checkForEndGame = (): AppThunk => (dispatch, getState) => {
40 | const state = getState();
41 | const player = getPlayer(state);
42 | const opponent = getOpponent(state);
43 |
44 | // if (hasLost(player) || hasWon(player)) {
45 | if (hasLost(player) || hasLost(opponent)) {
46 | dispatch(finishGame());
47 | }
48 | };
49 |
50 | export const endTurn = (): AppThunk => (dispatch, getState) => {
51 | // dispatch(triggerEvent({trigger: TriggerType.TurnEnd, id: }));
52 | dispatch(nextTurn());
53 | const game = getState();
54 | const player = getActivePlayer(game);
55 | Toastr.info(`${player.name}'s turn!`);
56 | const { id } = player;
57 |
58 | dispatch(gainMana({ id }));
59 | dispatch(restoreMana({ id }));
60 |
61 | const deck = _.values(ownerCards(id, game.deck));
62 | const hand = _.values(ownerCards(id, game.hand));
63 |
64 | if (deck.length > 0) {
65 | const topCard = deck[0];
66 | if (hand.length >= MAX_CARDS_IN_HAND) {
67 | Toastr.error(`Cant draw more cards from the ${player.name}'s deck!`);
68 | dispatch(burnCard({ id: topCard.id }));
69 | } else {
70 | dispatch(drawCard(topCard));
71 | }
72 | } else {
73 | Toastr.error(`No more cards left in the ${player.name}'s deck!`);
74 |
75 | dispatch(fatigueDamage({ id: player.id, heroId: player.heroID as number }));
76 | }
77 |
78 | dispatch(processDeaths());
79 | dispatch(checkForEndGame());
80 | };
81 |
82 | export default createReducer(
83 | initialState.state, // TODO: remove
84 | {
85 | [nextTurn.type]: nextTurnHandler,
86 | [finishGame.type]: finishGameHandler
87 | }
88 | );
89 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React & Redux based Hearthstone clone
2 | Built for educational purposes only. Your contributions are welcome 😉
3 |
4 | ### [PLAY DEMO](https://zernie.com/typescript-redux-card-game/)
5 |
6 | ## Project structure
7 | * `src/models` contains game models' type declarations and some helper functions.
8 | * `src/redux` contains the actual game logic, decoupled from the UI rendering. It uses **Redux** state library.
9 | It is highly recommended to use [redux-devtools](https://github.com/reduxjs/redux-devtools) Chrome extension for debugging.
10 | * `src/UI` is a **React**-based implementation of Game's UI.
11 |
12 | ## Development
13 | $ yarn && yarn start
14 |
15 | Now open [localhost:3000](http://localhost:3000) in your web browser :tada:
16 |
17 | ## Roadmap:
18 |
19 | ### Gameplay
20 | * [x] Stats
21 | * [x] Armor
22 | * [x] HP
23 | * [x] Mana
24 | * [ ] Overload
25 | * [x] Entities
26 | * [x] Player
27 | * [x] Hero
28 | * [x] Minion
29 | * [x] Weapon
30 | * [ ] Hero power
31 | * [ ] Spell
32 | * [ ] Secret
33 | * [ ] Quest
34 | * [ ] Sidequest
35 | * [ ] Enchantment
36 | * [ ] Aura
37 | * [x] Zones
38 | * [x] Play
39 | * [x] Hand
40 | * [x] Deck
41 | * [ ] Graveyard
42 | * [ ] Set aside
43 | * [ ] Secret
44 | * [ ] Removed from game
45 | * [x] Turns
46 | * [x] Player control switch
47 | * [x] Fatigue damage
48 | * [x] Card overdraw damage
49 | * [ ] 🕡 Turn timer
50 | (https://github.com/matpaul/redux-timer-middleware, https://stackoverflow.com/questions/3969475/javascript-pause-settimeout)
51 | * [x] Basic mechanics
52 | * [x] Charge
53 | * [x] Windfury
54 | * [x] Taunt
55 | * [ ] Battlecry (needs triggers)
56 | * [ ] Stealth (needs targeting improvements)
57 | * [ ] Poisonous
58 | * [ ] Divine shield
59 | * [ ] Mulligan (needs spells)
60 | * [ ] Choosing a deck
61 | * [ ] Card targeting
62 | * [ ] Play requirements
63 | * [ ] Deck import/export
64 |
65 | ### Visual
66 | * [x] Card rendering
67 | * [x] Basic
68 | * [ ] Use [Sunwell](https://github.com/HearthSim/Sunwell)?
69 | * [x] Drag & Drop cards (`react-dnd`)
70 | * [x] Touch screen support
71 | * [ ] Fix minion death animation bug (#10)
72 |
73 | ### Technical
74 | * [ ] Merge all game entities into `game.entities` (?)
75 | * [ ] Tests
76 | * [ ] [Sequences](https://hearthstone.gamepedia.com/Advanced_rulebook#Advanced_mechanics_101_.28READ_THIS_FIRST.29) (will require major refactoring)
77 | * [ ] Load cards from https://hearthstonejson.com
78 | * [ ] Save/Load
79 | * [ ] LocalStorage
80 | * [ ] File system
81 | * [ ] [Triggers](https://hearthstone.gamepedia.com/Triggered_effect)
82 | * [ ] ~~Multiplayer~~
83 |
--------------------------------------------------------------------------------
/src/redux/modules/initialState.ts:
--------------------------------------------------------------------------------
1 | // import HearthstoneJSON from "hearthstonejson-client";
2 | import _ from "lodash/fp";
3 | import { craftOpponent, Game, importCard, Minion, Weapon } from "../../models";
4 | import {
5 | EntityContainer,
6 | Player,
7 | CardContainer,
8 | Hero,
9 | CardClass,
10 | Step,
11 | Zone,
12 | makeEntityContainer,
13 | craftPlayer,
14 | craftHero
15 | } from "../../models";
16 | import { cards } from "../../cards";
17 |
18 | // PLAYERS
19 | const player: Player = craftPlayer({
20 | mana: 5,
21 | maximumMana: 5
22 | });
23 | const opponent: Player = craftOpponent();
24 |
25 | // HEROES
26 | const playerHero: Hero = importCard(
27 | cards["HERO_02"],
28 | Zone.Hand,
29 | player.id
30 | ) as Hero;
31 | const opponentHero: Hero = importCard(
32 | cards["HERO_01"],
33 | Zone.Hand,
34 | opponent.id
35 | ) as Hero;
36 |
37 | player.heroID = playerHero.id;
38 | opponent.heroID = opponentHero.id;
39 |
40 | // MINIONS
41 | const handMinions = makeEntityContainer([
42 | importCard(cards["CS2_173"], Zone.Hand, player.id) as Minion,
43 | importCard(cards["EX1_587"], Zone.Hand, player.id) as Minion,
44 | importCard(cards["CS2_200"], Zone.Hand, opponent.id) as Minion
45 | ]);
46 |
47 | const minions = makeEntityContainer([
48 | importCard(cards["CS2_189"], Zone.Play, opponent.id) as Minion,
49 | importCard(cards["CS2_121"], Zone.Play, player.id) as Minion,
50 | importCard(cards["CS2_147"], Zone.Play, opponent.id) as Minion
51 | ]);
52 |
53 | // WEAPONS
54 | const handWeapons = makeEntityContainer([
55 | importCard(cards["CS2_106"], Zone.Hand, opponent.id) as Weapon
56 | ]);
57 |
58 | // CARDS
59 | const hand: CardContainer = { ...handMinions, ...handWeapons };
60 |
61 | const deck = makeEntityContainer([
62 | importCard(cards["CS2_172"], Zone.Deck, player.id) as Minion,
63 | importCard(cards["CS2_141"], Zone.Deck, player.id) as Minion,
64 | importCard(cards["CS2_125"], Zone.Deck, player.id) as Minion,
65 | importCard(cards["CS2_182"], Zone.Deck, player.id) as Minion,
66 |
67 | importCard(cards["CS2_187"], Zone.Deck, opponent.id) as Minion,
68 | importCard(cards["CS2_121"], Zone.Deck, opponent.id) as Minion
69 | ]);
70 |
71 | // TODO: refactor
72 | export const play: EntityContainer = {
73 | ...makeEntityContainer([player, opponent, playerHero, opponentHero]),
74 | ...minions
75 | };
76 |
77 | const firstPlayerId = _.sample([player.id, opponent.id]) as number; // TODO: add a coin toss action
78 |
79 | const initialState: Game = {
80 | deck,
81 | hand,
82 | play,
83 | graveyard: {},
84 | secret: {},
85 | setAside: {},
86 | state: {
87 | activePlayer: firstPlayerId,
88 | step: Step.BeginFirst,
89 | playerID: player.id,
90 | opponentID: opponent.id,
91 | playerHeroID: playerHero.id,
92 | opponentHeroID: opponentHero.id,
93 | turn: 1
94 | }
95 | };
96 |
97 | export default initialState;
98 |
--------------------------------------------------------------------------------
/src/UI/Play/Battlefield.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useDrop } from "react-dnd";
3 | import { useDispatch } from "react-redux";
4 | import { Divider, Grid, Segment } from "semantic-ui-react";
5 | import classNames from "classnames";
6 | import { Card, canPlayCard, PLAYABLE_CARDS } from "../../models";
7 | import Side from "./Side";
8 | import NextTurn from "./NextTurn";
9 | import Deck from "../Deck/Deck";
10 | import EndGameScreen from "../EndGameScreen";
11 | import { endTurn } from "../../redux/modules/gameStateReducer";
12 | import { playerUseCard } from "../../redux/modules/handReducer";
13 | import Hand from "../Hand/Hand";
14 | import DnDHero from "./DnDHero";
15 | import {
16 | useGameState,
17 | useIsGameOver,
18 | useIsOpponentActive,
19 | useIsPlayerActive,
20 | useOpponent,
21 | useOpponentDeck,
22 | useOpponentHand,
23 | useOpponentHero,
24 | useOpponentMinions,
25 | usePlayer,
26 | usePlayerDeck,
27 | usePlayerHand,
28 | usePlayerHero,
29 | usePlayerMinions
30 | } from "../hooks";
31 |
32 | // TODO: split up
33 | const Battlefield: React.FC = (props) => {
34 | const dispatch = useDispatch();
35 | const { turn } = useGameState();
36 | const isPlayer = useIsPlayerActive();
37 | const isOpponent = useIsOpponentActive();
38 | const playerHero = usePlayerHero();
39 | const opponentHero = useOpponentHero();
40 | const player = usePlayer();
41 | const opponent = useOpponent();
42 | const playerMinions = usePlayerMinions();
43 | const opponentMinions = useOpponentMinions();
44 | const playerDeck = usePlayerDeck();
45 | const opponentDeck = useOpponentDeck();
46 | const playerHand = usePlayerHand();
47 | const opponentHand = useOpponentHand();
48 | const isGameOver = useIsGameOver();
49 |
50 | const [{ isOver, canDrop }, drop] = useDrop({
51 | accept: PLAYABLE_CARDS,
52 | drop: (item: Card, monitor) => dispatch(playerUseCard(item)),
53 | canDrop: (item: Card, monitor) => canPlayCard(item, player),
54 | collect: (monitor) => ({
55 | isOver: monitor.isOver(),
56 | canDrop: monitor.canDrop()
57 | })
58 | });
59 | const nextTurnHandler = () => dispatch(endTurn());
60 |
61 | return (
62 |
63 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | );
105 | };
106 |
107 | export default Battlefield;
108 |
--------------------------------------------------------------------------------
/src/redux/modules/play/characterReducer.ts:
--------------------------------------------------------------------------------
1 | import { createReducer, PayloadAction, Reducer } from "@reduxjs/toolkit";
2 | import reduceReducers from "reduce-reducers";
3 | import _ from "lodash/fp";
4 | import {
5 | AppThunk,
6 | Character,
7 | EntityContainer,
8 | getCharacters,
9 | getCharactersById,
10 | getEntity,
11 | isHero,
12 | isMinion,
13 | reduceArmor,
14 | reduceHealth,
15 | shouldBeDestroyed,
16 | shouldExhaust,
17 | TriggerType,
18 | Weapon
19 | } from "../../../models";
20 | import {
21 | attackCharacter,
22 | dealDamage,
23 | DealDamagePayload,
24 | destroyWeapon,
25 | exhaust,
26 | processDeaths,
27 | SourceTargetPayload,
28 | triggerEvent
29 | } from "./actions";
30 | import { CharacterHandler, extractEntity } from "../../utils";
31 | import minionReducer from "./minionReducer";
32 | import heroReducer from "./heroReducer";
33 | import { checkForEndGame, nextTurn } from "../gameStateReducer";
34 |
35 | export const attackBlock = ({
36 | target,
37 | source
38 | }: SourceTargetPayload): AppThunk => (dispatch, getState) => {
39 | dispatch(triggerEvent({ id: source.id, trigger: TriggerType.Attack }));
40 | dispatch(attackCharacter({ id: source.id }));
41 | dispatch(triggerEvent({ id: source.id, trigger: TriggerType.AfterAttack }));
42 | };
43 |
44 | export const dealDamageBlock = (
45 | source: number,
46 | target: number | number[],
47 | amount: number
48 | ): AppThunk => (dispatch, getState) => {
49 | dispatch(triggerEvent({ id: source, trigger: TriggerType.PreDamage }));
50 | dispatch(
51 | dealDamage({
52 | amount,
53 | id: target
54 | })
55 | );
56 | dispatch(triggerEvent({ id: source, trigger: TriggerType.DealDamage }));
57 | dispatch(triggerEvent({ id: target, trigger: TriggerType.TakeDamage }));
58 | };
59 |
60 | // TODO: refactor
61 | export const performAttack = ({
62 | target,
63 | source
64 | }: SourceTargetPayload): AppThunk => (dispatch, getState) => {
65 | dispatch(attackBlock({ source, target }));
66 | dispatch(dealDamageBlock(source.id, target.id, source.attack));
67 | const game = getState();
68 |
69 | // Hit back if target is a minion
70 | if (isMinion(target)) {
71 | dispatch(dealDamageBlock(target.id, source.id, target.attack));
72 | }
73 | if (isHero(source) && source.weaponID) {
74 | const weapon = getEntity(game.play, source.weaponID) as Weapon;
75 | if (weapon && weapon.health <= 0) {
76 | dispatch(destroyWeapon(weapon));
77 | }
78 | }
79 |
80 | const attacker = game.play[source.id] as Character;
81 | if (shouldExhaust(attacker)) {
82 | dispatch(exhaust({ id: attacker.id }));
83 | }
84 |
85 | dispatch(processDeaths());
86 | dispatch(checkForEndGame());
87 | };
88 |
89 | const attackCharacterHandler: CharacterHandler = (char: Character) => {
90 | char.attacksPerformed++;
91 | };
92 |
93 | const exhaustHandler: CharacterHandler = (char: Character) => {
94 | char.exhausted = true;
95 | };
96 |
97 | const dealDamageHandler = (
98 | state: EntityContainer,
99 | { payload: { amount, id } }: PayloadAction
100 | ) => {
101 | const chars = getCharactersById(state, _.castArray(id));
102 |
103 | _.forEach((char) => {
104 | char.health = reduceHealth(char, amount);
105 | // TODO: refactor
106 | if (isHero(char)) {
107 | char.armor = reduceArmor(char, amount);
108 | }
109 | char.destroyed = shouldBeDestroyed(char);
110 | }, chars);
111 | };
112 |
113 | const nextTurnHandler = (state: EntityContainer) => {
114 | const chars = getCharacters(state);
115 | _.forEach((char) => {
116 | char.attacksPerformed = 0;
117 | char.exhausted = false;
118 | }, chars);
119 | };
120 |
121 | const characterReducer = createReducer(
122 | {},
123 | {
124 | [exhaust.type]: extractEntity(exhaustHandler),
125 | [attackCharacter.type]: extractEntity(attackCharacterHandler),
126 | [dealDamage.type]: dealDamageHandler,
127 | [nextTurn.type]: nextTurnHandler
128 | }
129 | );
130 |
131 | export default reduceReducers(
132 | characterReducer,
133 | minionReducer,
134 | heroReducer
135 | ) as Reducer;
136 |
--------------------------------------------------------------------------------
/src/models/enums.ts:
--------------------------------------------------------------------------------
1 | // NOTE: some of these enums have been copied from https://github.com/HearthSim/SabberStone
2 |
3 | export enum BlockType {
4 | Attack = "ATTACK",
5 | Joust = "JOUST",
6 | Continuous = "CONTINUOUS",
7 | Power = "POWER",
8 | Script = "SCRIPT",
9 | Trigger = "TRIGGER",
10 | Deaths = "DEATHS",
11 | Play = "PLAY",
12 | Fatigue = "FATIGUE",
13 | Ritual = "RITUAL",
14 | RevealCard = "REVEAL_CARD",
15 | GameReset = "GAME_RESET",
16 | MoveMinion = "MOVE_MINION",
17 | Action = "ACTION"
18 | }
19 |
20 | export enum Locale {
21 | /** Default locale */
22 | AmericanEnglish = "enUS",
23 | English = "enGB",
24 | French = "frFR",
25 | German = "deDE",
26 | Korean = "koKR",
27 | Spanish = "esES",
28 | MexicanSpanish = "esMX",
29 | Russian = "ruRU",
30 | /** Used in Taiwan */
31 | TraditionalChinese = "zhTW",
32 | /** Used in Mainland China */
33 | Chinese = "zhCN",
34 | Italian = "itIT",
35 | BrazilianPortuguese = "ptBR",
36 | Polish = "plPL",
37 | Portuguese = "ptPT",
38 | Japanese = "jaJP",
39 | Thai = "thTH"
40 | }
41 |
42 | export enum CardClass {
43 | DeathKnight = "DEATHKNIGHT",
44 | Druid = "DRUID",
45 | Hunter = "HUNTER",
46 | Mage = "MAGE",
47 | Paladin = "PALADIN",
48 | Priest = "PRIEST",
49 | Rogue = "ROGUE",
50 | Shaman = "SHAMAN",
51 | Warlock = "WARLOCK",
52 | Warrior = "WARRIOR",
53 | Dream = "DREAM ",
54 | Neutral = "NEUTRAL"
55 | }
56 |
57 | export enum CardSet {
58 | // ### STANDARD ###
59 | Basic = "CORE",
60 | Classic = "EXPERT1",
61 |
62 | TheWitchwood = "GILNEAS",
63 | TheBoomsdayProject = "BOOMSDAY",
64 | RastakhansRumble = "TROLL",
65 | RiseOfShadows = "DALARAN",
66 | SaviorsOfUldum = "ULDUM",
67 | DescentOfDragons = "DRAGONS",
68 |
69 | // ### WILD ###
70 | HallOfFame = "HOF",
71 |
72 | CurseOfNaxxramas = "NAXX",
73 | GoblinsVSGnomes = "GVG",
74 | BlackrockMountain = "BRM",
75 | TheGrandTournament = "TGT",
76 | TheLeagueOfExplorers = "LOE",
77 | WhispersOfTheOldGods = "OG",
78 | OneNightInKarazhan = "KARA",
79 | MeanStreetsOfGadgetzan = "GANGS",
80 | JourneyToUnGoro = "UNGORO",
81 | KnightsOfTheFrozenThrone = "ICECROWN",
82 | KoboldsAndCatacombs = "LOOTAPALOOZA",
83 |
84 | // ### OTHER ###
85 | /** https://hearthstone.gamepedia.com/Tavern_Brawl */
86 | TavernBrawl = "TB",
87 | /** https://hearthstone.gamepedia.com/Mission */
88 | Missions = "MISSIONS",
89 | /** https://hearthstone.gamepedia.com/Battlegrounds */
90 | Battlegrounds = "BATTLEGROUNDS",
91 | /** https://hearthstone.gamepedia.com/Credits_card */
92 | Credits = "CREDITS",
93 | /** https://hearthstone.gamepedia.com/Alternate_hero */
94 | HeroSkins = "HERO_SKINS",
95 | /** https://hearthstone.gamepedia.com/Wild_Event */
96 | WildEvent = "WILD_EVENT",
97 | /** https://hearthstone.gamepedia.com/The_Taverns_of_Time*/
98 | TavernsOfTime = "TAVERNS_OF_TIME"
99 | }
100 |
101 | export enum CardType {
102 | Minion = "MINION",
103 | Weapon = "WEAPON",
104 | Hero = "HERO",
105 | Enchantment = "ENCHANTMENT",
106 | Spell = "SPELL",
107 | HeroPower = "HERO_POWER",
108 | Player = "PLAYER"
109 | }
110 |
111 | export enum Faction {
112 | Horde = "HORDE",
113 | Alliance = "ALLIANCE"
114 | }
115 |
116 | export enum MultiClassGroup {
117 | GrimyGoons = "GRIMY_GOONS",
118 | JadeLotus = "JADE_LOTUS",
119 | Kabal = "KABAL"
120 | }
121 |
122 | export enum Ability {
123 | AdjacentBuff = "ADJACENT_BUFF",
124 | AiMustPlay = "AI_MUST_PLAY",
125 | AppearFunctionallyDead = "APPEAR_FUNCTIONALLY_DEAD",
126 | Adapt = "ADAPT",
127 | Aura = "AURA",
128 | Battlecry = "BATTLECRY",
129 | CantAttack = "CANT_ATTACK",
130 | CantBeTargetedByAbilities = "CANT_BE_TARGETED_BY_ABILITIES",
131 | CantBeTargetedByHeroPowers = "CANT_BE_TARGETED_BY_HERO_POWERS",
132 | Charge = "CHARGE",
133 | ChooseOne = "CHOOSE_ONE",
134 | Combo = "COMBO",
135 | Counter = "COUNTER",
136 | Deathrattle = "DEATHRATTLE",
137 | Discover = "DISCOVER",
138 | DivineShield = "DIVINE_SHIELD",
139 | Enraged = "ENRAGED",
140 | EvilGlow = "EVIL_GLOW",
141 | Forgetful = "FORGETFUL",
142 | Freeze = "FREEZE",
143 | Immune = "IMMUNE",
144 | Inspire = "INSPIRE",
145 | JadeGolem = "JADE_GOLEM",
146 | Morph = "MORPH",
147 | Poisonous = "POISONOUS",
148 | Quest = "QUEST",
149 | ReceivesDoubleSpelldamageBonus = "RECEIVES_DOUBLE_SPELLDAMAGE_BONUS",
150 | Ritual = "RITUAL",
151 | Secret = "SECRET",
152 | Silence = "SILENCE",
153 | Stealth = "STEALTH",
154 | TagOneTurnEffect = "TAG_ONE_TURN_EFFECT",
155 | Taunt = "TAUNT",
156 | Topdeck = "TOPDECK",
157 | Untouchable = "UNTOUCHABLE",
158 | Windfury = "WINDFURY",
159 | ImmuneToSpellpower = "ImmuneToSpellpower",
160 | InvisibleDeathrattle = "InvisibleDeathrattle"
161 | }
162 |
163 | export enum Race {
164 | Blank = "BLANK",
165 | All = "ALL",
166 | Beast = "BEAST",
167 | Demon = "DEMON",
168 | Dragon = "DRAGON",
169 | Mechanical = "MECHANICAL",
170 | Murloc = "MURLOC",
171 | Pirate = "PIRATE",
172 | Totem = "TOTEM",
173 | Elemental = "ELEMENTAL"
174 | }
175 |
176 | export enum Rarity {
177 | Common = "COMMON",
178 | Free = "FREE",
179 | Rare = "RARE",
180 | Epic = "EPIC",
181 | Legendary = "LEGENDARY"
182 | }
183 |
184 | export interface CardData {
185 | id: string;
186 | dbfId: number;
187 |
188 | rarity?: Rarity;
189 | faction?: Faction;
190 | set: CardSet;
191 | cardClass?: CardClass;
192 | type: CardType;
193 | race?: Race;
194 | multiClassGroup?: MultiClassGroup;
195 |
196 | // localized
197 | name: string;
198 | text?: string;
199 | collectionText?: string;
200 | flavor?: string;
201 | howToEarn?: string;
202 | howToEarnGolden?: string;
203 | targetingArrowText?: string;
204 |
205 | // additional
206 | collectible?: boolean;
207 | elite?: boolean;
208 | cost?: number;
209 | attack?: number;
210 | health?: number;
211 | durability?: number;
212 | overload?: number;
213 | spellDamage?: number;
214 | armor?: number;
215 | techLevel?: number;
216 | hideStats?: boolean;
217 |
218 | /** Dual-class characters */
219 | classes?: [CardClass, CardClass];
220 | mechanics?: Ability[];
221 | referencedTags?: (Ability | string)[];
222 | artist?: string;
223 | }
224 |
225 | export type Controller = number;
226 |
227 | /* export enum Controller {
228 | Player = "PLAYER",
229 | Opponent = "OPPONENT"
230 | }*/
231 |
232 | /** Player state */
233 | export enum PlayState {
234 | Invalid = "INVALID",
235 | Playing = "PLAYING",
236 | // Winning = "WINNING",
237 | // Losing = "LOSING",
238 | Won = "WON",
239 | Lost = "LOST",
240 | Tied = "TIED" // TODO
241 | // Disconnected = "DISCONNECTED",
242 | // Conceded = "CONCEDED",
243 | // Quit = "QUIT"
244 | }
245 |
246 | /** Game step */
247 | export enum Step {
248 | Invalid = "INVALID",
249 | BeginFirst = "BEGIN_FIRST",
250 | BeginShuffle = "BEGIN_SHUFFLE",
251 | BeginDraw = "BEGIN_DRAW",
252 | BeginMulligan = "BEGIN_MULLIGAN",
253 | MainBegin = "MAIN_BEGIN",
254 | MainReady = "MAIN_READY",
255 | MainResource = "MAIN_RESOURCE",
256 | MainDraw = "MAIN_DRAW",
257 | MainStart = "MAIN_START",
258 | MainAction = "MAIN_ACTION",
259 | MainCombat = "MAIN_COMBAT",
260 | MainEnd = "MAIN_END",
261 | MainNext = "MAIN_NEXT",
262 | FinalWrapUp = "FINAL_WRAPUP",
263 | FinalGameOver = "FINAL_GAMEOVER",
264 | MainCleanup = "MAIN_CLEANUP",
265 | MainStartTriggers = "MAIN_START_TRIGGERS"
266 | }
267 |
268 | /** Entity zone */
269 | export enum Zone {
270 | Invalid = "INVALID",
271 | Play = "PLAY",
272 | Deck = "DECK",
273 | Hand = "HAND",
274 | Graveyard = "GRAVEYARD",
275 | RemovedFromGame = "REMOVEDFROMGAME",
276 | SetAside = "SETASIDE",
277 | Secret = "SECRET"
278 | }
279 |
280 | export enum EntityType {
281 | Invalid = "INVALID",
282 |
283 | /**
284 | All cards on the stack
285 | */
286 | Stack = "STACK",
287 |
288 | /**
289 | The target
290 | */
291 | Target = "TARGET",
292 |
293 | /**
294 | The source
295 | */
296 | Source = "SOURCE",
297 |
298 | /**
299 | Player's heroID
300 | */
301 | Hero = "HERO",
302 |
303 | /**
304 | Player's heroID power
305 | */
306 | HeroPower = "HERO_POWER",
307 |
308 | /**
309 | Opponent's heroID power
310 | */
311 | OpHeroPower = "OP_HERO_POWER",
312 |
313 | /**
314 | The target of the current event. (e.g. the defender, the target of a spell, or the just damaged or healed
315 | character)
316 | */
317 | EventTarget = "EVENT_TARGET",
318 |
319 | /**
320 | The source of the current event. (e.g. the attacker, the played card, or the just damaging or healing entity)
321 | */
322 | EventSource = "EVENT_SOURCE",
323 |
324 | /**
325 | The top card from the playerID's deck
326 | */
327 | TopCardFromDeck = "TOPCARDFROMDECK",
328 |
329 | /**
330 | The top card from the opponentID's deck
331 | */
332 | OpTopDeck = "OP_TOPDECK",
333 |
334 | /**
335 | All cards in the playerID's hand
336 | */
337 | Hand = "HAND",
338 |
339 | /**
340 | All cards in the playerID's deck
341 | */
342 | Deck = "DECK",
343 |
344 | /**
345 | Player's secrets
346 | */
347 | Secrets = "SECRETS",
348 |
349 | /**
350 | Player's minions
351 | */
352 | Minions = "MINIONS",
353 |
354 | /**
355 | Player's minions except the source
356 | */
357 | MinionsNoSource = "MINIONS_NOSOURCE",
358 |
359 | /**
360 | All friends characters
361 | */
362 | Friends = "FRIENDS",
363 |
364 | /**
365 | Opponent's Hero
366 | */
367 | OpHero = "OP_HERO",
368 |
369 | /**
370 | All cards in the opponentID's hand
371 | */
372 | OpHand = "OP_HAND",
373 |
374 | /**
375 | All cards in the opponentID's deck
376 | */
377 | OpDeck = "OP_DECK",
378 |
379 | /**
380 | All opponentID secret
381 | */
382 | OpSecrets = "OP_SECRETS",
383 |
384 | /**
385 | All opponentID minion
386 | */
387 | OpMinions = "OP_MINIONS",
388 |
389 | /**
390 | All opponentID character
391 | */
392 | Enemies = "ENEMIES",
393 |
394 | /**
395 | All opponentID character except the source
396 | */
397 | EnemiesNoTarget = "ENEMIES_NOTARGET",
398 |
399 | /**
400 | All characters
401 | */
402 | All = "ALL",
403 |
404 | /**
405 | All characters except the source
406 | */
407 | AllNoSource = "ALL_NOSOURCE",
408 |
409 | /**
410 | Player's weapon
411 | */
412 | Weapon = "WEAPON",
413 |
414 | /**
415 | Opponent's weapon
416 | */
417 | OpWeapon = "OP_WEAPON",
418 |
419 | /**
420 | All minions
421 | */
422 | AlLMinions = "ALLMINIONS",
423 |
424 | /**
425 | All minions except the source
426 | */
427 | AllMinionsNoSource = "ALLMINIONS_NOSOURCE",
428 |
429 | /**
430 | All cards in the graveyard
431 | */
432 | Graveyard = "GRAVEYARD",
433 |
434 | /**
435 | All heroes
436 | */
437 | Heroes = "HEROES",
438 |
439 | /**
440 | The Controller entity of the playerID
441 | */
442 | Controller = "CONTROLLER",
443 |
444 | /**
445 | The Controller entity of the opponentID.
446 | */
447 | OpController = "OP_CONTROLLER",
448 |
449 | /**
450 | The cards have been discarded from the playerID's hand.
451 | */
452 | Discarded = "DISCARDED"
453 | }
454 |
455 | export enum PlayReq {
456 | REQ_MINION_TARGET = 1,
457 | REQ_FRIENDLY_TARGET = 2,
458 | REQ_ENEMY_TARGET = 3,
459 | REQ_DAMAGED_TARGET = 4,
460 | REQ_MAX_SECRETS = 5,
461 | REQ_FROZEN_TARGET = 6,
462 | REQ_CHARGE_TARGET = 7,
463 | REQ_TARGET_MAX_ATTACK = 8,
464 | REQ_NONSELF_TARGET = 9,
465 | REQ_TARGET_WITH_RACE = 10,
466 | REQ_TARGET_TO_PLAY = 11,
467 | REQ_NUM_MINION_SLOTS = 12,
468 | REQ_WEAPON_EQUIPPED = 13,
469 | REQ_ENOUGH_MANA = 14,
470 | REQ_YOUR_TURN = 15,
471 | REQ_NONSTEALTH_ENEMY_TARGET = 16,
472 | REQ_HERO_TARGET = 17,
473 | REQ_SECRET_ZONE_CAP = 18,
474 | REQ_MINION_CAP_IF_TARGET_AVAILABLE = 19,
475 | REQ_MINION_CAP = 20,
476 | REQ_TARGET_ATTACKED_THIS_TURN = 21,
477 | REQ_TARGET_IF_AVAILABLE = 22,
478 | REQ_MINIMUM_ENEMY_MINIONS = 23,
479 | REQ_TARGET_FOR_COMBO = 24,
480 | REQ_NOT_EXHAUSTED_ACTIVATE = 25,
481 | REQ_UNIQUE_SECRET_OR_QUEST = 26,
482 | REQ_TARGET_TAUNTER = 27,
483 | REQ_CAN_BE_ATTACKED = 28,
484 | REQ_ACTION_PWR_IS_MASTER_PWR = 29,
485 | REQ_TARGET_MAGNET = 30,
486 | REQ_ATTACK_GREATER_THAN_0 = 31,
487 | REQ_ATTACKER_NOT_FROZEN = 32,
488 | REQ_HERO_OR_MINION_TARGET = 33,
489 | REQ_CAN_BE_TARGETED_BY_SPELLS = 34,
490 | REQ_SUBCARD_IS_PLAYABLE = 35,
491 | REQ_TARGET_FOR_NO_COMBO = 36,
492 | REQ_NOT_MINION_JUST_PLAYED = 37,
493 | REQ_NOT_EXHAUSTED_HERO_POWER = 38,
494 | REQ_CAN_BE_TARGETED_BY_OPPONENTS = 39,
495 | REQ_ATTACKER_CAN_ATTACK = 40,
496 | REQ_TARGET_MIN_ATTACK = 41,
497 | REQ_CAN_BE_TARGETED_BY_HERO_POWERS = 42,
498 | REQ_ENEMY_TARGET_NOT_IMMUNE = 43,
499 | REQ_ENTIRE_ENTOURAGE_NOT_IN_PLAY = 44,
500 | REQ_MINIMUM_TOTAL_MINIONS = 45,
501 | REQ_MUST_TARGET_TAUNTER = 46,
502 | REQ_UNDAMAGED_TARGET = 47,
503 | REQ_CAN_BE_TARGETED_BY_BATTLECRIES = 48,
504 | REQ_STEADY_SHOT = 49,
505 | REQ_MINION_OR_ENEMY_HERO = 50,
506 | REQ_TARGET_IF_AVAILABLE_AND_DRAGON_IN_HAND = 51,
507 | REQ_LEGENDARY_TARGET = 52,
508 | REQ_FRIENDLY_MINION_DIED_THIS_TURN = 53,
509 | REQ_FRIENDLY_MINION_DIED_THIS_GAME = 54,
510 | REQ_ENEMY_WEAPON_EQUIPPED = 55,
511 | REQ_TARGET_IF_AVAILABLE_AND_MINIMUM_FRIENDLY_MINIONS = 56,
512 | REQ_TARGET_WITH_BATTLECRY = 57,
513 | REQ_TARGET_WITH_DEATHRATTLE = 58,
514 | REQ_TARGET_IF_AVAILABLE_AND_MINIMUM_FRIENDLY_SECRETS = 59,
515 | REQ_SECRET_ZONE_CAP_FOR_NON_SECRET = 60,
516 | REQ_TARGET_EXACT_COST = 61,
517 | REQ_STEALTHED_TARGET = 62,
518 | REQ_MINION_SLOT_OR_MANA_CRYSTAL_SLOT = 63,
519 | REQ_MAX_QUESTS = 64,
520 | REQ_TARGET_IF_AVAILABE_AND_ELEMENTAL_PLAYED_LAST_TURN = 65,
521 | REQ_TARGET_NOT_VAMPIRE = 66,
522 | REQ_TARGET_NOT_DAMAGEABLE_ONLY_BY_WEAPONS = 67,
523 | REQ_NOT_DISABLED_HERO_POWER = 68,
524 | REQ_MUST_PLAY_OTHER_CARD_FIRST = 69,
525 | REQ_HAND_NOT_FULL = 70,
526 | REQ_TARGET_IF_AVAILABLE_AND_NO_3_COST_CARD_IN_DECK = 71,
527 | REQ_CAN_BE_TARGETED_BY_COMBOS = 72,
528 | REQ_CANNOT_PLAY_THIS = 73,
529 | REQ_FRIENDLY_MINIONS_OF_RACE_DIED_THIS_GAME = 74,
530 | REQ_DRAG_TO_PLAY_PRE29933 = 75,
531 | REQ_OPPONENT_PLAYED_CARDS_THIS_GAME = 77,
532 | REQ_LITERALLY_UNPLAYABLE = 78,
533 | REQ_TARGET_IF_AVAILABLE_AND_HERO_HAS_ATTACK = 79,
534 | REQ_FRIENDLY_MINION_OF_RACE_DIED_THIS_TURN = 80,
535 | REQ_TARGET_IF_AVAILABLE_AND_MINIMUM_SPELLS_PLAYED_THIS_TURN = 81,
536 | REQ_FRIENDLY_MINION_OF_RACE_IN_HAND = 82,
537 | REQ_DRAG_TO_PLAY_PRE31761 = 83,
538 | REQ_MANA_CRYSTAL = 84,
539 | REQ85 = 85,
540 | REQ_FRIENDLY_DEATHRATTLE_MINION_DIED_THIS_GAME = 86,
541 | REQ87 = 87,
542 | REQ88 = 88,
543 | REQ_FRIENDLY_REBORN_MINION_DIED_THIS_GAME = 89,
544 | REQ_MINION_DIED_THIS_GAME = 90,
545 | REQ_BOARD_NOT_COMPLETELY_FULL = 92,
546 | REQ_TARGET_IF_AVAILABLE_AND_HAS_OVERLOADED_MANA = 93,
547 | REQ_DRAG_TO_PLAY = 94
548 | }
549 |
550 | /**
551 | Indicates which (group of) entities the effect will influence.
552 | */
553 | export enum PowerArea {
554 | /**
555 | There will be no effect.
556 | */
557 | NONE,
558 |
559 | /**
560 | The effect will be triggered by the selected target (target chosen by playerID).
561 | */
562 | TARGET,
563 |
564 | /**
565 | The effect will be triggered by the heroID of the controller.
566 | */
567 | HERO,
568 |
569 | /**
570 | The effect will be triggered by the heroID of the opponentID controller.
571 | */
572 | OP_HERO,
573 |
574 | /**
575 | The effect will be triggered by the heroID of all controllers.
576 | */
577 | HEROES,
578 |
579 | /**
580 | The effect will be triggered by the board of the controller.
581 | */
582 | BOARD,
583 |
584 | /**
585 | The effect will be triggered by the board of the opponentID controller.
586 | */
587 | OP_BOARD,
588 |
589 | /**
590 | The effect will be triggered by the board of all controllers.
591 | */
592 | BOARDS,
593 |
594 | /**
595 | The effect will be triggered by the hand of the controller.
596 | */
597 | HAND,
598 |
599 | /**
600 | The effect will be triggered by the hand of the opponentID controller.
601 | */
602 | OP_HAND,
603 |
604 | /**
605 | The effect will be triggered by the hand of all controllers.
606 | */
607 | HANDS,
608 |
609 | /**
610 | The effect will be triggered by the entity itself.
611 | */
612 | SELF,
613 |
614 | /**
615 | The effect will be triggered by the GAME entity.
616 | */
617 | GAME,
618 |
619 | /**
620 | The effect will be triggered by the controller entity.
621 | */
622 | CONTROLLER,
623 |
624 | /**
625 | The effect will be triggered by the opponentID controller entity.
626 | */
627 | OP_CONTROLLER,
628 |
629 | /**
630 | The effect will be triggered by all controller entities.
631 | */
632 | CONTROLLERS,
633 |
634 | /**
635 | The effect will be triggered by the secrets of the controller.
636 | */
637 | SECRET,
638 |
639 | ///
640 | /// The effect will be triggered by the secrets of all controllers.
641 | ///
642 | SECRETS,
643 |
644 | /**
645 | The effect will be triggered by the HAND and BOARD of the controller.
646 | */
647 | HAND_AND_BOARD,
648 |
649 | /**
650 | The effect will be triggered by the HAND and BOARD of the opponentID controller.
651 | */
652 | OP_BOARD_AND_OP_HERO,
653 |
654 | /**
655 | The effect will be triggered by the HERO and BOARD of all controllers.
656 | */
657 | BOARDS_HEROES,
658 |
659 | /**
660 | This effect will be triggered by the GRAVEYARD of the controller.
661 | */
662 | GRAVEYARD,
663 |
664 | /**
665 | This effect will be triggered by the GRAVEYARD and SECRET of the controller.
666 | */
667 | GRAVEYARD_AND_SECRET,
668 |
669 | /**
670 | This effect will be triggered by the GRAVEYARD of the opponentID controller.
671 | */
672 | OP_GRAVEYARD,
673 |
674 | /**
675 | This effect will be triggered by the GRAVEYARD and SECRET of the opponentID controller.
676 | */
677 | OP_GRAVEYARD_AND_OP_SECRET
678 | }
679 |
680 | export enum TargetingType {
681 | None,
682 | All,
683 | FriendlyCharacters,
684 | EnemyCharacters,
685 | AllMinions,
686 | FriendlyMinions,
687 | EnemyMinions,
688 | Heroes
689 | }
690 |
691 | /** Events in Hearthstone. */
692 | export enum TriggerType {
693 | None = "NONE",
694 |
695 | MultiTrigger = "MULTITRIGGER",
696 |
697 | /* The effect will be triggered at the end of turn.*/
698 | TurnEnd = "TURN_END",
699 |
700 | /* The effect will be triggered at the start of turn.*/
701 | TurnStart = "TURN_START",
702 |
703 | /* The effect will be triggered when a minion dies.*/
704 | Death = "DEATH",
705 |
706 | /* The effect will be triggered after a playerID uses Hero Power.*/
707 | Inspire = "INSPIRE",
708 |
709 | /* The effect will be triggered when a character is damaged.*/
710 | DealDamage = "DEAL_DAMAGE",
711 |
712 | /* The effect will be triggered when a spell or a character deals damages.*/
713 | TakeDamage = "TAKE_DAMAGE",
714 | /*
715 | effect will be triggered when a character gets Predamage.
716 | This event happens just before the character is actually damaged.
717 | */
718 | PreDamage = "PREDAMAGE",
719 |
720 | /* The effect will be triggered when characters are healed.*/
721 | Heal = "HEAL",
722 |
723 | /* The effect will be triggered when a minion loses its Divine Shield*/
724 | LoseDivineShield = "LOSE_DIVINE_SHIELD",
725 |
726 | /* The effect will be triggered when characters attack.*/
727 | Attack = "ATTACK",
728 |
729 | /* The effect will be triggered after an attack action is ended.*/
730 | AfterAttack = "AFTER_ATTACK",
731 |
732 | /* The effect will be triggered whenever a minion is summoned*/
733 | Summon = "SUMMON",
734 | /* The effect will be triggered after a minion is summoned.*/
735 | AfterSummon = "AFTER_SUMMON",
736 |
737 | /* The effect will be triggered when a playerID plays a card.*/
738 | PlayCard = "PLAY_CARD",
739 |
740 | /* The effect will be triggered after a playerID plays a card.*/
741 | AfterPlayCard = "AFTER_PLAY_CARD",
742 | /* The effect will be triggered when a playerID plays a Minion card.*/
743 | PlayMinion = "PLAY_MINION",
744 |
745 | /* The effect will be triggered after a minion is played.*/
746 | AfterPlayMinion = "AFTER_PLAY_MINION",
747 |
748 | /* The effect will be triggered when a playerID plays a Spell card.*/
749 | CastSpell = "CAST_SPELL",
750 |
751 | /* The effect will be triggered after a spell is played.*/
752 | AfterCast = "AFTER_CAST",
753 |
754 | /* The effect will be triggered when a secret is activated.*/
755 | SecretRevealed = "SECRET_REVEALED",
756 | /* The effect will be triggered when an entity enters any types of zone.*/
757 | Zone = "ZONE",
758 |
759 | /* The effect will be triggered when a card is discarded from hand.*/
760 | Discard = "DISCARD",
761 |
762 | /* The effect will be triggered when a game begins.*/
763 | GameStart = "GAME_START",
764 | /* The effect will be triggered when a card is drawed.*/
765 | Draw = "DRAW",
766 |
767 | /* The effect will be triggered when a card is targeted by an attacking minion or a played card.*/
768 | Target = "TARGET",
769 |
770 | /* The effect will be triggered when a entity is frozen.*/
771 | Frozen = "FROZEN",
772 |
773 | /* The effect will be triggered when a heroID gains armor.*/
774 | Armor = "ARMOR",
775 |
776 | /* The effect will be triggered when a heroID equips a weapon.*/
777 | EquipWeapon = "EQUIP_WEAPON",
778 |
779 | /* The effect will be triggered when a card is shuffled into a deck.*/
780 | ShuffleIntoDeck = "SHUFFLE_INTO_DECK",
781 |
782 | Overload = "OVERLOAD"
783 |
784 | // "WORGEN_TRANSFORM"
785 | }
786 |
787 | /*
788 | Types of entity that can invoke an event.
789 | */
790 | export enum TriggerSource {
791 | ALL,
792 | FRIENDLY,
793 | ENEMY,
794 | SELF,
795 | MINIONS,
796 | MINIONS_EXCEPT_SELF,
797 | OP_MINIONS,
798 | ALL_MINIONS,
799 | ALL_MINIONS_EXCEPT_SELF,
800 | HERO,
801 | OP_HERO,
802 | ENCHANTMENT_TARGET,
803 | WEAPON,
804 | HERO_POWER,
805 | FRIENDLY_SPELL_CASTED_ON_THE_OWNER,
806 | FRIENDLY_SPELL_CASTED_ON_OWN_MINIONS,
807 | FRIENDLY_EVENT_SOURCE,
808 | ADJACENT_TO_THE_OWNER
809 | }
810 |
811 | /*
812 | Indicates where this trigger should be activated.
813 | */
814 | export enum TriggerActivation {
815 | PLAY,
816 | HAND,
817 | DECK,
818 | HAND_OR_PLAY
819 | }
820 |
821 | /*
822 | Types of event sequences in Hearthstone.
823 | */
824 | export enum SequenceType {
825 | None,
826 | PlayCard,
827 | PlayMinion,
828 | PlaySpell,
829 | Summon,
830 | DamageDealt,
831 | Attack,
832 | Target
833 | }
834 |
835 | export enum PlayType {
836 | PlayerPlay,
837 | PlayerDraw,
838 | PlayerGet,
839 | PlayerMulligan,
840 | PlayerHandDiscard,
841 | PlayerDeckDiscard,
842 | PlayerBackToHand,
843 | PlayerSecretPlayed,
844 | PlayerHeroPower,
845 | PlayerPlayToDeck,
846 | PlayerGetToDeck,
847 | OpponentPlay,
848 | OpponentDraw,
849 | OpponentGet,
850 | OpponentMulligan,
851 | OpponentHandDiscard,
852 | OpponentDeckDiscard,
853 | OpponentPlayToDeck,
854 | OpponentBackToHand,
855 | OpponentSecretPlayed,
856 | OpponentSecretTriggered,
857 | OpponentHeroPower,
858 | OpponentGetToDeck
859 | }
860 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 | Preamble
9 |
10 | The GNU General Public License is a free, copyleft license for
11 | software and other kinds of works.
12 |
13 | The licenses for most software and other practical works are designed
14 | to take away your freedom to share and change the works. By contrast,
15 | the GNU General Public License is intended to guarantee your freedom to
16 | share and change all versions of a program--to make sure it remains free
17 | software for all its users. We, the Free Software Foundation, use the
18 | GNU General Public License for most of our software; it applies also to
19 | any other work released this way by its authors. You can apply it to
20 | your programs, too.
21 |
22 | When we speak of free software, we are referring to freedom, not
23 | price. Our General Public Licenses are designed to make sure that you
24 | have the freedom to distribute copies of free software (and charge for
25 | them if you wish), that you receive source code or can get it if you
26 | want it, that you can change the software or use pieces of it in new
27 | free programs, and that you know you can do these things.
28 |
29 | To protect your rights, we need to prevent others from denying you
30 | these rights or asking you to surrender the rights. Therefore, you have
31 | certain responsibilities if you distribute copies of the software, or if
32 | you modify it: responsibilities to respect the freedom of others.
33 |
34 | For example, if you distribute copies of such a program, whether
35 | gratis or for a fee, you must pass on to the recipients the same
36 | freedoms that you received. You must make sure that they, too, receive
37 | or can get the source code. And you must show them these terms so they
38 | know their rights.
39 |
40 | Developers that use the GNU GPL protect your rights with two steps:
41 | (1) assert copyright on the software, and (2) offer you this License
42 | giving you legal permission to copy, distribute and/or modify it.
43 |
44 | For the developers' and authors' protection, the GPL clearly explains
45 | that there is no warranty for this free software. For both users' and
46 | authors' sake, the GPL requires that modified versions be marked as
47 | changed, so that their problems will not be attributed erroneously to
48 | authors of previous versions.
49 |
50 | Some devices are designed to deny users access to install or run
51 | modified versions of the software inside them, although the manufacturer
52 | can do so. This is fundamentally incompatible with the aim of
53 | protecting users' freedom to change the software. The systematic
54 | pattern of such abuse occurs in the area of products for individuals to
55 | use, which is precisely where it is most unacceptable. Therefore, we
56 | have designed this version of the GPL to prohibit the practice for those
57 | products. If such problems arise substantially in other domains, we
58 | stand ready to extend this provision to those domains in future versions
59 | of the GPL, as needed to protect the freedom of users.
60 |
61 | Finally, every program is threatened constantly by software patents.
62 | States should not allow patents to restrict development and use of
63 | software on general-purpose computers, but in those that do, we wish to
64 | avoid the special danger that patents applied to a free program could
65 | make it effectively proprietary. To prevent this, the GPL assures that
66 | patents cannot be used to render the program non-free.
67 |
68 | The precise terms and conditions for copying, distribution and
69 | modification follow.
70 |
71 | TERMS AND CONDITIONS
72 |
73 | 0. Definitions.
74 |
75 | "This License" refers to version 3 of the GNU General Public License.
76 |
77 | "Copyright" also means copyright-like laws that apply to other kinds of
78 | works, such as semiconductor masks.
79 |
80 | "The Program" refers to any copyrightable work licensed under this
81 | License. Each licensee is addressed as "you". "Licensees" and
82 | "recipients" may be individuals or organizations.
83 |
84 | To "modify" a work means to copy from or adapt all or part of the work
85 | in a fashion requiring copyright permission, other than the making of an
86 | exact copy. The resulting work is called a "modified version" of the
87 | earlier work or a work "based on" the earlier work.
88 |
89 | A "covered work" means either the unmodified Program or a work based
90 | on the Program.
91 |
92 | To "propagate" a work means to do anything with it that, without
93 | permission, would make you directly or secondarily liable for
94 | infringement under applicable copyright law, except executing it on a
95 | computer or modifying a private copy. Propagation includes copying,
96 | distribution (with or without modification), making available to the
97 | public, and in some countries other activities as well.
98 |
99 | To "convey" a work means any kind of propagation that enables other
100 | parties to make or receive copies. Mere interaction with a user through
101 | a computer network, with no transfer of a copy, is not conveying.
102 |
103 | An interactive user interface displays "Appropriate Legal Notices"
104 | to the extent that it includes a convenient and prominently visible
105 | feature that (1) displays an appropriate copyright notice, and (2)
106 | tells the user that there is no warranty for the work (except to the
107 | extent that warranties are provided), that licensees may convey the
108 | work under this License, and how to view a copy of this License. If
109 | the interface presents a list of user commands or options, such as a
110 | menu, a prominent item in the list meets this criterion.
111 |
112 | 1. Source Code.
113 |
114 | The "source code" for a work means the preferred form of the work
115 | for making modifications to it. "Object code" means any non-source
116 | form of a work.
117 |
118 | A "Standard Interface" means an interface that either is an official
119 | standard defined by a recognized standards body, or, in the case of
120 | interfaces specified for a particular programming language, one that
121 | is widely used among developers working in that language.
122 |
123 | The "System Libraries" of an executable work include anything, other
124 | than the work as a whole, that (a) is included in the normal form of
125 | packaging a Major Component, but which is not part of that Major
126 | Component, and (b) serves only to enable use of the work with that
127 | Major Component, or to implement a Standard Interface for which an
128 | implementation is available to the public in source code form. A
129 | "Major Component", in this context, means a major essential component
130 | (kernel, window system, and so on) of the specific operating system
131 | (if any) on which the executable work runs, or a compiler used to
132 | produce the work, or an object code interpreter used to run it.
133 |
134 | The "Corresponding Source" for a work in object code form means all
135 | the source code needed to generate, install, and (for an executable
136 | work) run the object code and to modify the work, including scripts to
137 | control those activities. However, it does not include the work's
138 | System Libraries, or general-purpose tools or generally available free
139 | programs which are used unmodified in performing those activities but
140 | which are not part of the work. For example, Corresponding Source
141 | includes interface definition files associated with source files for
142 | the work, and the source code for shared libraries and dynamically
143 | linked subprograms that the work is specifically designed to require,
144 | such as by intimate data communication or control flow between those
145 | subprograms and other parts of the work.
146 |
147 | The Corresponding Source need not include anything that users
148 | can regenerate automatically from other parts of the Corresponding
149 | Source.
150 |
151 | The Corresponding Source for a work in source code form is that
152 | same work.
153 |
154 | 2. Basic Permissions.
155 |
156 | All rights granted under this License are granted for the term of
157 | copyright on the Program, and are irrevocable provided the stated
158 | conditions are met. This License explicitly affirms your unlimited
159 | permission to run the unmodified Program. The output from running a
160 | covered work is covered by this License only if the output, given its
161 | content, constitutes a covered work. This License acknowledges your
162 | rights of fair use or other equivalent, as provided by copyright law.
163 |
164 | You may make, run and propagate covered works that you do not
165 | convey, without conditions so long as your license otherwise remains
166 | in force. You may convey covered works to others for the sole purpose
167 | of having them make modifications exclusively for you, or provide you
168 | with facilities for running those works, provided that you comply with
169 | the terms of this License in conveying all material for which you do
170 | not control copyright. Those thus making or running the covered works
171 | for you must do so exclusively on your behalf, under your direction
172 | and control, on terms that prohibit them from making any copies of
173 | your copyrighted material outside their relationship with you.
174 |
175 | Conveying under any other circumstances is permitted solely under
176 | the conditions stated below. Sublicensing is not allowed; section 10
177 | makes it unnecessary.
178 |
179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
180 |
181 | No covered work shall be deemed part of an effective technological
182 | measure under any applicable law fulfilling obligations under article
183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or
184 | similar laws prohibiting or restricting circumvention of such
185 | measures.
186 |
187 | When you convey a covered work, you waive any legal power to forbid
188 | circumvention of technological measures to the extent such circumvention
189 | is effected by exercising rights under this License with respect to
190 | the covered work, and you disclaim any intention to limit operation or
191 | modification of the work as a means of enforcing, against the work's
192 | users, your or third parties' legal rights to forbid circumvention of
193 | technological measures.
194 |
195 | 4. Conveying Verbatim Copies.
196 |
197 | You may convey verbatim copies of the Program's source code as you
198 | receive it, in any medium, provided that you conspicuously and
199 | appropriately publish on each copy an appropriate copyright notice;
200 | keep intact all notices stating that this License and any
201 | non-permissive terms added in accord with section 7 apply to the code;
202 | keep intact all notices of the absence of any warranty; and give all
203 | recipients a copy of this License along with the Program.
204 |
205 | You may charge any price or no price for each copy that you convey,
206 | and you may offer support or warranty protection for a fee.
207 |
208 | 5. Conveying Modified Source Versions.
209 |
210 | You may convey a work based on the Program, or the modifications to
211 | produce it from the Program, in the form of source code under the
212 | terms of section 4, provided that you also meet all of these conditions:
213 |
214 | a) The work must carry prominent notices stating that you modified
215 | it, and giving a relevant date.
216 |
217 | b) The work must carry prominent notices stating that it is
218 | released under this License and any conditions added under section
219 | 7. This requirement modifies the requirement in section 4 to
220 | "keep intact all notices".
221 |
222 | c) You must license the entire work, as a whole, under this
223 | License to anyone who comes into possession of a copy. This
224 | License will therefore apply, along with any applicable section 7
225 | additional terms, to the whole of the work, and all its parts,
226 | regardless of how they are packaged. This License gives no
227 | permission to license the work in any other way, but it does not
228 | invalidate such permission if you have separately received it.
229 |
230 | d) If the work has interactive user interfaces, each must display
231 | Appropriate Legal Notices; however, if the Program has interactive
232 | interfaces that do not display Appropriate Legal Notices, your
233 | work need not make them do so.
234 |
235 | A compilation of a covered work with other separate and independent
236 | works, which are not by their nature extensions of the covered work,
237 | and which are not combined with it such as to form a larger program,
238 | in or on a volume of a storage or distribution medium, is called an
239 | "aggregate" if the compilation and its resulting copyright are not
240 | used to limit the access or legal rights of the compilation's users
241 | beyond what the individual works permit. Inclusion of a covered work
242 | in an aggregate does not cause this License to apply to the other
243 | parts of the aggregate.
244 |
245 | 6. Conveying Non-Source Forms.
246 |
247 | You may convey a covered work in object code form under the terms
248 | of sections 4 and 5, provided that you also convey the
249 | machine-readable Corresponding Source under the terms of this License,
250 | in one of these ways:
251 |
252 | a) Convey the object code in, or embodied in, a physical product
253 | (including a physical distribution medium), accompanied by the
254 | Corresponding Source fixed on a durable physical medium
255 | customarily used for software interchange.
256 |
257 | b) Convey the object code in, or embodied in, a physical product
258 | (including a physical distribution medium), accompanied by a
259 | written offer, valid for at least three years and valid for as
260 | long as you offer spare parts or customer support for that product
261 | model, to give anyone who possesses the object code either (1) a
262 | copy of the Corresponding Source for all the software in the
263 | product that is covered by this License, on a durable physical
264 | medium customarily used for software interchange, for a price no
265 | more than your reasonable cost of physically performing this
266 | conveying of source, or (2) access to copy the
267 | Corresponding Source from a network server at no charge.
268 |
269 | c) Convey individual copies of the object code with a copy of the
270 | written offer to provide the Corresponding Source. This
271 | alternative is allowed only occasionally and noncommercially, and
272 | only if you received the object code with such an offer, in accord
273 | with subsection 6b.
274 |
275 | d) Convey the object code by offering access from a designated
276 | place (gratis or for a charge), and offer equivalent access to the
277 | Corresponding Source in the same way through the same place at no
278 | further charge. You need not require recipients to copy the
279 | Corresponding Source along with the object code. If the place to
280 | copy the object code is a network server, the Corresponding Source
281 | may be on a different server (operated by you or a third party)
282 | that supports equivalent copying facilities, provided you maintain
283 | clear directions next to the object code saying where to find the
284 | Corresponding Source. Regardless of what server hosts the
285 | Corresponding Source, you remain obligated to ensure that it is
286 | available for as long as needed to satisfy these requirements.
287 |
288 | e) Convey the object code using peer-to-peer transmission, provided
289 | you inform other peers where the object code and Corresponding
290 | Source of the work are being offered to the general public at no
291 | charge under subsection 6d.
292 |
293 | A separable portion of the object code, whose source code is excluded
294 | from the Corresponding Source as a System Library, need not be
295 | included in conveying the object code work.
296 |
297 | A "User Product" is either (1) a "consumer product", which means any
298 | tangible personal property which is normally used for personal, family,
299 | or household purposes, or (2) anything designed or sold for incorporation
300 | into a dwelling. In determining whether a product is a consumer product,
301 | doubtful cases shall be resolved in favor of coverage. For a particular
302 | product received by a particular user, "normally used" refers to a
303 | typical or common use of that class of product, regardless of the status
304 | of the particular user or of the way in which the particular user
305 | actually uses, or expects or is expected to use, the product. A product
306 | is a consumer product regardless of whether the product has substantial
307 | commercial, industrial or non-consumer uses, unless such uses represent
308 | the only significant mode of use of the product.
309 |
310 | "Installation Information" for a User Product means any methods,
311 | procedures, authorization keys, or other information required to install
312 | and execute modified versions of a covered work in that User Product from
313 | a modified version of its Corresponding Source. The information must
314 | suffice to ensure that the continued functioning of the modified object
315 | code is in no case prevented or interfered with solely because
316 | modification has been made.
317 |
318 | If you convey an object code work under this section in, or with, or
319 | specifically for use in, a User Product, and the conveying occurs as
320 | part of a transaction in which the right of possession and use of the
321 | User Product is transferred to the recipient in perpetuity or for a
322 | fixed term (regardless of how the transaction is characterized), the
323 | Corresponding Source conveyed under this section must be accompanied
324 | by the Installation Information. But this requirement does not apply
325 | if neither you nor any third party retains the ability to install
326 | modified object code on the User Product (for example, the work has
327 | been installed in ROM).
328 |
329 | The requirement to provide Installation Information does not include a
330 | requirement to continue to provide support service, warranty, or updates
331 | for a work that has been modified or installed by the recipient, or for
332 | the User Product in which it has been modified or installed. Access to a
333 | network may be denied when the modification itself materially and
334 | adversely affects the operation of the network or violates the rules and
335 | protocols for communication across the network.
336 |
337 | Corresponding Source conveyed, and Installation Information provided,
338 | in accord with this section must be in a format that is publicly
339 | documented (and with an implementation available to the public in
340 | source code form), and must require no special password or key for
341 | unpacking, reading or copying.
342 |
343 | 7. Additional Terms.
344 |
345 | "Additional permissions" are terms that supplement the terms of this
346 | License by making exceptions from one or more of its conditions.
347 | Additional permissions that are applicable to the entire Program shall
348 | be treated as though they were included in this License, to the extent
349 | that they are valid under applicable law. If additional permissions
350 | apply only to part of the Program, that part may be used separately
351 | under those permissions, but the entire Program remains governed by
352 | this License without regard to the additional permissions.
353 |
354 | When you convey a copy of a covered work, you may at your option
355 | remove any additional permissions from that copy, or from any part of
356 | it. (Additional permissions may be written to require their own
357 | removal in certain cases when you modify the work.) You may place
358 | additional permissions on material, added by you to a covered work,
359 | for which you have or can give appropriate copyright permission.
360 |
361 | Notwithstanding any other provision of this License, for material you
362 | add to a covered work, you may (if authorized by the copyright holders of
363 | that material) supplement the terms of this License with terms:
364 |
365 | a) Disclaiming warranty or limiting liability differently from the
366 | terms of sections 15 and 16 of this License; or
367 |
368 | b) Requiring preservation of specified reasonable legal notices or
369 | author attributions in that material or in the Appropriate Legal
370 | Notices displayed by works containing it; or
371 |
372 | c) Prohibiting misrepresentation of the origin of that material, or
373 | requiring that modified versions of such material be marked in
374 | reasonable ways as different from the original version; or
375 |
376 | d) Limiting the use for publicity purposes of names of licensors or
377 | authors of the material; or
378 |
379 | e) Declining to grant rights under trademark law for use of some
380 | trade names, trademarks, or service marks; or
381 |
382 | f) Requiring indemnification of licensors and authors of that
383 | material by anyone who conveys the material (or modified versions of
384 | it) with contractual assumptions of liability to the recipient, for
385 | any liability that these contractual assumptions directly impose on
386 | those licensors and authors.
387 |
388 | All other non-permissive additional terms are considered "further
389 | restrictions" within the meaning of section 10. If the Program as you
390 | received it, or any part of it, contains a notice stating that it is
391 | governed by this License along with a term that is a further
392 | restriction, you may remove that term. If a license document contains
393 | a further restriction but permits relicensing or conveying under this
394 | License, you may add to a covered work material governed by the terms
395 | of that license document, provided that the further restriction does
396 | not survive such relicensing or conveying.
397 |
398 | If you add terms to a covered work in accord with this section, you
399 | must place, in the relevant source files, a statement of the
400 | additional terms that apply to those files, or a notice indicating
401 | where to find the applicable terms.
402 |
403 | Additional terms, permissive or non-permissive, may be stated in the
404 | form of a separately written license, or stated as exceptions;
405 | the above requirements apply either way.
406 |
407 | 8. Termination.
408 |
409 | You may not propagate or modify a covered work except as expressly
410 | provided under this License. Any attempt otherwise to propagate or
411 | modify it is void, and will automatically terminate your rights under
412 | this License (including any patent licenses granted under the third
413 | paragraph of section 11).
414 |
415 | However, if you cease all violation of this License, then your
416 | license from a particular copyright holder is reinstated (a)
417 | provisionally, unless and until the copyright holder explicitly and
418 | finally terminates your license, and (b) permanently, if the copyright
419 | holder fails to notify you of the violation by some reasonable means
420 | prior to 60 days after the cessation.
421 |
422 | Moreover, your license from a particular copyright holder is
423 | reinstated permanently if the copyright holder notifies you of the
424 | violation by some reasonable means, this is the first time you have
425 | received notice of violation of this License (for any work) from that
426 | copyright holder, and you cure the violation prior to 30 days after
427 | your receipt of the notice.
428 |
429 | Termination of your rights under this section does not terminate the
430 | licenses of parties who have received copies or rights from you under
431 | this License. If your rights have been terminated and not permanently
432 | reinstated, you do not qualify to receive new licenses for the same
433 | material under section 10.
434 |
435 | 9. Acceptance Not Required for Having Copies.
436 |
437 | You are not required to accept this License in order to receive or
438 | run a copy of the Program. Ancillary propagation of a covered work
439 | occurring solely as a consequence of using peer-to-peer transmission
440 | to receive a copy likewise does not require acceptance. However,
441 | nothing other than this License grants you permission to propagate or
442 | modify any covered work. These actions infringe copyright if you do
443 | not accept this License. Therefore, by modifying or propagating a
444 | covered work, you indicate your acceptance of this License to do so.
445 |
446 | 10. Automatic Licensing of Downstream Recipients.
447 |
448 | Each time you convey a covered work, the recipient automatically
449 | receives a license from the original licensors, to run, modify and
450 | propagate that work, subject to this License. You are not responsible
451 | for enforcing compliance by third parties with this License.
452 |
453 | An "entity transaction" is a transaction transferring control of an
454 | organization, or substantially all assets of one, or subdividing an
455 | organization, or merging organizations. If propagation of a covered
456 | work results from an entity transaction, each party to that
457 | transaction who receives a copy of the work also receives whatever
458 | licenses to the work the party's predecessor in interest had or could
459 | give under the previous paragraph, plus a right to possession of the
460 | Corresponding Source of the work from the predecessor in interest, if
461 | the predecessor has it or can get it with reasonable efforts.
462 |
463 | You may not impose any further restrictions on the exercise of the
464 | rights granted or affirmed under this License. For example, you may
465 | not impose a license fee, royalty, or other charge for exercise of
466 | rights granted under this License, and you may not initiate litigation
467 | (including a cross-claim or counterclaim in a lawsuit) alleging that
468 | any patent claim is infringed by making, using, selling, offering for
469 | sale, or importing the Program or any portion of it.
470 |
471 | 11. Patents.
472 |
473 | A "contributor" is a copyright holder who authorizes use under this
474 | License of the Program or a work on which the Program is based. The
475 | work thus licensed is called the contributor's "contributor version".
476 |
477 | A contributor's "essential patent claims" are all patent claims
478 | owned or controlled by the contributor, whether already acquired or
479 | hereafter acquired, that would be infringed by some manner, permitted
480 | by this License, of making, using, or selling its contributor version,
481 | but do not include claims that would be infringed only as a
482 | consequence of further modification of the contributor version. For
483 | purposes of this definition, "control" includes the right to grant
484 | patent sublicenses in a manner consistent with the requirements of
485 | this License.
486 |
487 | Each contributor grants you a non-exclusive, worldwide, royalty-free
488 | patent license under the contributor's essential patent claims, to
489 | make, use, sell, offer for sale, import and otherwise run, modify and
490 | propagate the contents of its contributor version.
491 |
492 | In the following three paragraphs, a "patent license" is any express
493 | agreement or commitment, however denominated, not to enforce a patent
494 | (such as an express permission to practice a patent or covenant not to
495 | sue for patent infringement). To "grant" such a patent license to a
496 | party means to make such an agreement or commitment not to enforce a
497 | patent against the party.
498 |
499 | If you convey a covered work, knowingly relying on a patent license,
500 | and the Corresponding Source of the work is not available for anyone
501 | to copy, free of charge and under the terms of this License, through a
502 | publicly available network server or other readily accessible means,
503 | then you must either (1) cause the Corresponding Source to be so
504 | available, or (2) arrange to deprive yourself of the benefit of the
505 | patent license for this particular work, or (3) arrange, in a manner
506 | consistent with the requirements of this License, to extend the patent
507 | license to downstream recipients. "Knowingly relying" means you have
508 | actual knowledge that, but for the patent license, your conveying the
509 | covered work in a country, or your recipient's use of the covered work
510 | in a country, would infringe one or more identifiable patents in that
511 | country that you have reason to believe are valid.
512 |
513 | If, pursuant to or in connection with a single transaction or
514 | arrangement, you convey, or propagate by procuring conveyance of, a
515 | covered work, and grant a patent license to some of the parties
516 | receiving the covered work authorizing them to use, propagate, modify
517 | or convey a specific copy of the covered work, then the patent license
518 | you grant is automatically extended to all recipients of the covered
519 | work and works based on it.
520 |
521 | A patent license is "discriminatory" if it does not include within
522 | the scope of its coverage, prohibits the exercise of, or is
523 | conditioned on the non-exercise of one or more of the rights that are
524 | specifically granted under this License. You may not convey a covered
525 | work if you are a party to an arrangement with a third party that is
526 | in the business of distributing software, under which you make payment
527 | to the third party based on the extent of your activity of conveying
528 | the work, and under which the third party grants, to any of the
529 | parties who would receive the covered work from you, a discriminatory
530 | patent license (a) in connection with copies of the covered work
531 | conveyed by you (or copies made from those copies), or (b) primarily
532 | for and in connection with specific products or compilations that
533 | contain the covered work, unless you entered into that arrangement,
534 | or that patent license was granted, prior to 28 March 2007.
535 |
536 | Nothing in this License shall be construed as excluding or limiting
537 | any implied license or other defenses to infringement that may
538 | otherwise be available to you under applicable patent law.
539 |
540 | 12. No Surrender of Others' Freedom.
541 |
542 | If conditions are imposed on you (whether by court order, agreement or
543 | otherwise) that contradict the conditions of this License, they do not
544 | excuse you from the conditions of this License. If you cannot convey a
545 | covered work so as to satisfy simultaneously your obligations under this
546 | License and any other pertinent obligations, then as a consequence you may
547 | not convey it at all. For example, if you agree to terms that obligate you
548 | to collect a royalty for further conveying from those to whom you convey
549 | the Program, the only way you could satisfy both those terms and this
550 | License would be to refrain entirely from conveying the Program.
551 |
552 | 13. Use with the GNU Affero General Public License.
553 |
554 | Notwithstanding any other provision of this License, you have
555 | permission to link or combine any covered work with a work licensed
556 | under version 3 of the GNU Affero General Public License into a single
557 | combined work, and to convey the resulting work. The terms of this
558 | License will continue to apply to the part which is the covered work,
559 | but the special requirements of the GNU Affero General Public License,
560 | section 13, concerning interaction through a network will apply to the
561 | combination as such.
562 |
563 | 14. Revised Versions of this License.
564 |
565 | The Free Software Foundation may publish revised and/or new versions of
566 | the GNU General Public License from time to time. Such new versions will
567 | be similar in spirit to the present version, but may differ in detail to
568 | address new problems or concerns.
569 |
570 | Each version is given a distinguishing version number. If the
571 | Program specifies that a certain numbered version of the GNU General
572 | Public License "or any later version" applies to it, you have the
573 | option of following the terms and conditions either of that numbered
574 | version or of any later version published by the Free Software
575 | Foundation. If the Program does not specify a version number of the
576 | GNU General Public License, you may choose any version ever published
577 | by the Free Software Foundation.
578 |
579 | If the Program specifies that a proxy can decide which future
580 | versions of the GNU General Public License can be used, that proxy's
581 | public statement of acceptance of a version permanently authorizes you
582 | to choose that version for the Program.
583 |
584 | Later license versions may give you additional or different
585 | permissions. However, no additional obligations are imposed on any
586 | author or copyright holder as a result of your choosing to follow a
587 | later version.
588 |
589 | 15. Disclaimer of Warranty.
590 |
591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
599 |
600 | 16. Limitation of Liability.
601 |
602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
610 | SUCH DAMAGES.
611 |
612 | 17. Interpretation of Sections 15 and 16.
613 |
614 | If the disclaimer of warranty and limitation of liability provided
615 | above cannot be given local legal effect according to their terms,
616 | reviewing courts shall apply local law that most closely approximates
617 | an absolute waiver of all civil liability in connection with the
618 | Program, unless a warranty or assumption of liability accompanies a
619 | copy of the Program in return for a fee.
620 |
621 | END OF TERMS AND CONDITIONS
622 |
623 | How to Apply These Terms to Your New Programs
624 |
625 | If you develop a new program, and you want it to be of the greatest
626 | possible use to the public, the best way to achieve this is to make it
627 | free software which everyone can redistribute and change under these terms.
628 |
629 | To do so, attach the following notices to the program. It is safest
630 | to attach them to the start of each source file to most effectively
631 | state the exclusion of warranty; and each file should have at least
632 | the "copyright" line and a pointer to where the full notice is found.
633 |
634 |
635 | Copyright (C)
636 |
637 | This program is free software: you can redistribute it and/or modify
638 | it under the terms of the GNU General Public License as published by
639 | the Free Software Foundation, either version 3 of the License, or
640 | (at your option) any later version.
641 |
642 | This program is distributed in the hope that it will be useful,
643 | but WITHOUT ANY WARRANTY; without even the implied warranty of
644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
645 | GNU General Public License for more details.
646 |
647 | You should have received a copy of the GNU General Public License
648 | along with this program. If not, see .
649 |
650 | Also add information on how to contact you by electronic and paper mail.
651 |
652 | If the program does terminal interaction, make it output a short
653 | notice like this when it starts in an interactive mode:
654 |
655 | Copyright (C)
656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
657 | This is free software, and you are welcome to redistribute it
658 | under certain conditions; type `show c' for details.
659 |
660 | The hypothetical commands `show w' and `show c' should show the appropriate
661 | parts of the General Public License. Of course, your program's commands
662 | might be different; for a GUI interface, you would use an "about box".
663 |
664 | You should also get your employer (if you work as a programmer) or school,
665 | if any, to sign a "copyright disclaimer" for the program, if necessary.
666 | For more information on this, and how to apply and follow the GNU GPL, see
667 | .
668 |
669 | The GNU General Public License does not permit incorporating your program
670 | into proprietary programs. If your program is a subroutine library, you
671 | may consider it more useful to permit linking proprietary applications with
672 | the library. If this is what you want to do, use the GNU Lesser General
673 | Public License instead of this License. But first, please read
674 | .
675 |
--------------------------------------------------------------------------------