You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
4.6 KiB
TypeScript
165 lines
4.6 KiB
TypeScript
import {enableMapSet, createDraft, finishDraft, Draft} from 'immer'
|
|
import GameState, { GameObject, Room, Door, Item, ObjectType } from './types/GameState'
|
|
import ParsedCommand, { TokenType, ParsedTokenExpression, ValidCommandDetails, InvalidCommandDetails } from './types/ParsedCommand'
|
|
|
|
enableMapSet()
|
|
|
|
type ChangeListener = (state : GameState) => void
|
|
|
|
|
|
export type CommandValidateResult = {
|
|
validCommands: ValidCommandDetails [],
|
|
invalidCommands: InvalidCommandDetails []
|
|
}
|
|
|
|
export default class Game {
|
|
private gameState : GameState = {directions: new Map(), rooms: new Map(), doors: new Map(), items: new Map(), player: {location: ''}}
|
|
private draft : Draft<GameState> | null = null
|
|
private onChangeListeners : ChangeListener [] = []
|
|
|
|
constructor() {
|
|
console.log('adding directions')
|
|
let state = this.getState()
|
|
state.directions.set('north', {type: ObjectType.Direction, name: 'north', aliases: ['n']})
|
|
state.directions.set('east', {type: ObjectType.Direction, name: 'east', aliases: ['e']})
|
|
state.directions.set('south', {type: ObjectType.Direction, name: 'south', aliases: ['s']})
|
|
state.directions.set('west', {type: ObjectType.Direction, name: 'west', aliases: ['w']})
|
|
state.directions.set('up', {type: ObjectType.Direction, name: 'up', aliases: ['u']})
|
|
state.directions.set('down', {type: ObjectType.Direction, name: 'down', aliases: ['d']})
|
|
this.saveDraft()
|
|
}
|
|
|
|
filterValidCommands(commands: ParsedCommand[]) : CommandValidateResult {
|
|
let validCommands : ValidCommandDetails[] = []
|
|
let invalidCommands : InvalidCommandDetails[] = []
|
|
|
|
for(const command of commands) {
|
|
const commandValidationResult = command.areNounsValid(this)
|
|
|
|
if(commandValidationResult.isValid){
|
|
validCommands.push(commandValidationResult)
|
|
} else {
|
|
invalidCommands.push(commandValidationResult)
|
|
}
|
|
}
|
|
|
|
invalidCommands.sort((a,b) => a.severity - b.severity)
|
|
|
|
return {validCommands, invalidCommands}
|
|
}
|
|
|
|
onChange(callback : ChangeListener) {
|
|
this.onChangeListeners.push(callback)
|
|
}
|
|
|
|
beginDraft() {
|
|
if(this.draft)
|
|
console.warn('Destroying already created gamestate draft')
|
|
|
|
this.draft = createDraft(this.gameState)
|
|
}
|
|
|
|
saveDraft() {
|
|
if(!this.draft)
|
|
throw new Error('Game has no open draft state')
|
|
|
|
this.gameState = finishDraft(this.draft)
|
|
this.draft = null
|
|
|
|
for(const callback of this.onChangeListeners)
|
|
callback(this.gameState)
|
|
}
|
|
|
|
getState() : Draft<GameState> {
|
|
if(!this.draft)
|
|
this.beginDraft()
|
|
|
|
return this.draft!
|
|
}
|
|
|
|
getCurrentRoom() : Draft<Room> | null {
|
|
const state = this.getState()
|
|
return Array.from(state.rooms.values())
|
|
.find(room => room.name === state.player.location) || null
|
|
}
|
|
|
|
addRoom(room : Room) {
|
|
let state = this.getState()
|
|
state.rooms.set(room.name, room)
|
|
}
|
|
|
|
addDoor(door: Door) {
|
|
let state = this.getState()
|
|
state.doors.set(door.name, door)
|
|
}
|
|
|
|
addItem(item: Item) {
|
|
let state = this.getState()
|
|
state.items.set(item.name, item)
|
|
}
|
|
|
|
findObjectByName(name : string, type : ObjectType) : GameObject | null {
|
|
let collection
|
|
switch(type) {
|
|
case ObjectType.Door:
|
|
collection = this.getState().doors
|
|
break
|
|
|
|
case ObjectType.Item:
|
|
collection = this.getState().items
|
|
break
|
|
|
|
case ObjectType.Room:
|
|
collection = this.getState().rooms
|
|
break
|
|
|
|
case ObjectType.Direction:
|
|
collection = this.getState().directions
|
|
break
|
|
}
|
|
|
|
const objects = [...collection!.values()]
|
|
|
|
const exactMatch = objects.find((object) => name === object.name)
|
|
if(exactMatch)
|
|
return exactMatch
|
|
|
|
const aliasMatch = objects.find(({aliases}) => aliases.includes(name))
|
|
if(aliasMatch)
|
|
return aliasMatch
|
|
|
|
return null
|
|
}
|
|
|
|
isVisible(object : GameObject) : boolean {
|
|
const state = this.getState()
|
|
const currentRoom = this.getCurrentRoom()
|
|
|
|
switch(object.type) {
|
|
case ObjectType.Direction:
|
|
return true
|
|
|
|
case ObjectType.Room:
|
|
return state.player.location === (object as Room).name
|
|
|
|
case ObjectType.Door:
|
|
if(!currentRoom)
|
|
return false;
|
|
|
|
const neighborIDs = Array.from(currentRoom.neighbors.values())
|
|
|
|
let neighbors = neighborIDs.map(name => state.doors.get(name)).filter(object => object !== undefined).map(o => o!)
|
|
for(const neighbor of neighbors)
|
|
if(neighbor.name === object.name)
|
|
return true;
|
|
return false
|
|
|
|
case ObjectType.Item:
|
|
return state.player.location === (object as Item).location
|
|
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
}
|