Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keyboard controller support #91

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
53 changes: 47 additions & 6 deletions src/cli/stella/SystemConfigSetupProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import CommandInterpreter from '../CommandInterpreter';
import Factory from '../../machine/cpu/Factory';

export default class SystemConfigSetupProvider {
constructor(private _config: Config) {}
constructor(private _config: Config) { }

getCommands(): CommandInterpreter.CommandTableInterface {
return this._commands;
Expand Down Expand Up @@ -67,12 +67,52 @@ export default class SystemConfigSetupProvider {
return `audio ${this._config.enableAudio ? 'enabled' : 'disabled'}`;
}

protected _setupPaddles(args?: Array<string>) {
protected _setupControllerPort0(args?: Array<string>) {
if (args && args.length !== 0) {
this._config.emulatePaddles = this._isArgTruthy(args[0]);
this._config.controllerPort0 = this._parseControllerType(args[0]);
}

return `paddle emulation: ${this._config.emulatePaddles ? 'enabled' : 'disabled'}`;
return `current controller port 0: ${this._humanReadableControllerType(this._config.controllerPort0)}`;
}

protected _setupControllerPort1(args?: Array<string>) {
if (args && args.length !== 0) {
this._config.controllerPort1 = this._parseControllerType(args[0]);
}

return `current controller port 1: ${this._humanReadableControllerType(this._config.controllerPort1)}`;
}

protected _parseControllerType(value: string) {
switch (value.toLowerCase()) {
case 'joystick':
return Config.ControllerType.joystick;

case 'paddles':
return Config.ControllerType.paddles;

case 'keypad':
return Config.ControllerType.keypad;

default:
throw new Error(`invalid controller type "${value}"`);
}
}

protected _humanReadableControllerType(controllerType: Config.ControllerType) {
switch (controllerType) {
case Config.ControllerType.joystick:
return 'joystick';

case Config.ControllerType.paddles:
return 'paddles';

case Config.ControllerType.keypad:
return 'keypad';

default:
throw new Error(`invalid controller type "${controllerType}"`);
}
}

protected _setHighPrecisionCpu(args?: Array<string>) {
Expand Down Expand Up @@ -112,7 +152,7 @@ export default class SystemConfigSetupProvider {
this._config.pcmAudio = this._isArgTruthy(args[0]);
}

return `PCM audio emulation: ${this._config.emulatePaddles ? 'enabled' : 'disabled'}`;
return `PCM audio emulation: ${this._config.pcmAudio ? 'enabled' : 'disabled'}`;
}

protected _isArgTruthy(arg: string): boolean {
Expand All @@ -124,7 +164,8 @@ export default class SystemConfigSetupProvider {
_commands: CommandInterpreter.CommandTableInterface = {
'tv-mode': this._setupVideo.bind(this),
audio: this._setupAudio.bind(this),
paddles: this._setupPaddles.bind(this),
'controller-port-0': this._setupControllerPort0.bind(this),
'controller-port-1': this._setupControllerPort1.bind(this),
seed: this._setRandomSeed.bind(this),
pcm: this._setupPcmAUdio.bind(this),
'high-precision-cpu': this._setHighPrecisionCpu.bind(this)
Expand Down
9 changes: 8 additions & 1 deletion src/frontend/elm/Stellerator/Main.elm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ export const enum Scaling {
none = 'none',
}

export const enum ControllerType {
joystick = 'joystick',
paddles = 'paddles',
keypad = 'keypad',
}

export interface EmulationStateStopped {
state: EmulationStateKey.stopped;
}
Expand All @@ -106,7 +112,8 @@ export interface Cartridge {
name: string;
cartridgeType: CartridgeInfo.CartridgeType;
tvMode: TvMode;
emulatePaddles: boolean;
controllerPort0: ControllerType;
controllerPort1: ControllerType;
volume: number;
rngSeed?: number;
firstVisibleLine?: number;
Expand Down
49 changes: 46 additions & 3 deletions src/frontend/elm/Stellerator/Model.elm
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ module Stellerator.Model exposing
, ChangeSettingsMsg(..)
, ColorSwitch(..)
, ConsoleSwitches
, ControllerType(..)
, CpuEmulation(..)
, DifficultySwitch(..)
, EmulationState(..)
Expand All @@ -51,6 +52,7 @@ module Stellerator.Model exposing
, decodeAudioEmulation
, decodeCartridge
, decodeCartridgeType
, decodeControllerType
, decodeCpuEmulation
, decodeEmulationState
, decodeInputDriverEvent
Expand Down Expand Up @@ -139,12 +141,19 @@ type Scaling
| ScalingNone


type ControllerType
= ControllerTypeJoystick
| ControllerTypePaddles
| ControllerTypeKeypad


type alias Cartridge =
{ hash : String
, name : String
, cartridgeType : String
, tvMode : TvMode
, emulatePaddles : Bool
, controllerPort0 : ControllerType
, controllerPort1 : ControllerType
, volume : Int
, rngSeed : Maybe Int
, firstVisibleLine : Maybe Int
Expand Down Expand Up @@ -246,6 +255,8 @@ type ChangeCartridgeMsg
| ChangeCartridgeType String
| ChangeCartridgeTvMode TvMode
| ChangeCartridgeEmulatePaddles Bool
| ChangeCartridgeControllerPort0 ControllerType
| ChangeCartridgeControllerPort1 ControllerType
| ChangeCartridgeRngSeed (Maybe Int)
| ChangeCartridgeFirstVisibleLine (Maybe Int)
| ChangeCartridgeCpuEmulation (Maybe CpuEmulation)
Expand Down Expand Up @@ -600,6 +611,36 @@ encodeScaling scaling =
ScalingNone ->
Encode.string "none"

decodeControllerType : Decode.Decoder ControllerType
decodeControllerType =
Decode.string
|> Decode.andThen
(\s ->
case s of
"joystick" ->
Decode.succeed ControllerTypeJoystick

"paddles" ->
Decode.succeed ControllerTypePaddles

"keypad" ->
Decode.succeed ControllerTypeKeypad

_ ->
Decode.fail "invalid controller value"
)

encodeControllerType : ControllerType -> Encode.Value
encodeControllerType scaling =
case scaling of
ControllerTypeJoystick ->
Encode.string "joystick"

ControllerTypePaddles ->
Encode.string "paddles"

ControllerTypeKeypad ->
Encode.string "keypad"

decodeSettings : Decode.Decoder Settings
decodeSettings =
Expand Down Expand Up @@ -645,7 +686,8 @@ decodeCartridge =
|> Pipeline.required "name" Decode.string
|> Pipeline.required "cartridgeType" Decode.string
|> Pipeline.required "tvMode" decodeTvMode
|> Pipeline.required "emulatePaddles" Decode.bool
|> Pipeline.required "controllerPort0" decodeControllerType
|> Pipeline.required "controllerPort1" decodeControllerType
|> Pipeline.required "volume" Decode.int
|> Pipeline.optional "rngSeed" (Decode.maybe Decode.int) Nothing
|> Pipeline.optional "firstVisibleLine" (Decode.maybe Decode.int) Nothing
Expand All @@ -660,7 +702,8 @@ encodeCartridge cartridge =
, ( "name", Encode.string cartridge.name )
, ( "cartridgeType", Encode.string cartridge.cartridgeType )
, ( "tvMode", encodeTvMode cartridge.tvMode )
, ( "emulatePaddles", Encode.bool cartridge.emulatePaddles )
, ( "controllerPort0", encodeControllerType cartridge.controllerPort0 )
, ( "controllerPort1", encodeControllerType cartridge.controllerPort1 )
, ( "volume", Encode.int cartridge.volume )
]
|> encodeOptional "rngSeed" Encode.int cartridge.rngSeed
Expand Down
7 changes: 5 additions & 2 deletions src/frontend/elm/Stellerator/Update.elm
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,11 @@ updateCartridge cartridgeTypes msg cartridge =
ChangeCartridgeTvMode tvMode ->
{ cartridge | tvMode = tvMode }

ChangeCartridgeEmulatePaddles emulatePaddles ->
{ cartridge | emulatePaddles = emulatePaddles }
ChangeCartridgeControllerPort0 controllerPort0 ->
{ cartridge | controllerPort0 = controllerPort0 }

ChangeCartridgeControllerPort1 controllerPort1 ->
{ cartridge | controllerPort1 = controllerPort1 }

ChangeCartridgeRngSeed seed ->
let
Expand Down
15 changes: 13 additions & 2 deletions src/frontend/elm/Stellerator/View/Cartridges.elm
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import Stellerator.Model
, Cartridge
, CartridgeViewMode(..)
, ChangeCartridgeMsg(..)
, ControllerType(..)
, CpuEmulation(..)
, Media(..)
, Model
Expand Down Expand Up @@ -192,8 +193,18 @@ settingsItems model cart =
[ ( TvPAL, "PAL" ), ( TvNTSC, "NTSC" ), ( TvSECAM, "SECAM" ) ]
(changeCartridge ChangeCartridgeTvMode)
cart.tvMode
, checkbox "Emulate paddles:" <|
Form.checkbox (changeCartridge ChangeCartridgeEmulatePaddles) cart.emulatePaddles
, oneline "Controller Port 0:" <|
Form.radioGroup
[]
[ ( ControllerTypeJoystick, "Joystick" ), ( ControllerTypePaddles, "Paddles" ), ( ControllerTypeKeypad, "Keypad" ) ]
(changeCartridge ChangeCartridgeControllerPort0)
cart.controllerPort0
, oneline "Controller Port 1:" <|
Form.radioGroup
[]
[ ( ControllerTypeJoystick, "Joystick" ), ( ControllerTypePaddles, "Paddles" ), ( ControllerTypeKeypad, "Keypad" ) ]
(changeCartridge ChangeCartridgeControllerPort1)
cart.controllerPort1
, oneline "RNG seed:" <| optionalNumberInput ChangeCartridgeRngSeed cart.rngSeed
, oneline "First visible line:" <| optionalNumberInput ChangeCartridgeFirstVisibleLine cart.firstVisibleLine
, oneline "CPU emulation:" <|
Expand Down
7 changes: 4 additions & 3 deletions src/frontend/stellerator/service/AddCartridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@

import { injectable, inject } from 'inversify';
import JSZip from '@progress/jszip-esm';
import { Ports, TvMode } from '../../elm/Stellerator/Main.elm';
import { ControllerType, Ports, TvMode } from '../../elm/Stellerator/Main.elm';
import { calculateFromUint8Array as md5sum } from '../../../tools/hash/md5';
import CartridgeDetector from '../../../machine/stella/cartridge/CartridgeDetector';
import Storage, { CartridgeWithImage } from './Storage';

@injectable()
class AddCartridge {
constructor(@inject(Storage) private _storage: Storage) {}
constructor(@inject(Storage) private _storage: Storage) { }

init(ports: Ports): void {
this._ports = ports;
Expand Down Expand Up @@ -117,7 +117,8 @@ class AddCartridge {
name,
tvMode,
cartridgeType,
emulatePaddles: false,
controllerPort0: ControllerType.joystick,
controllerPort1: ControllerType.joystick,
volume: 100,
},
image: content,
Expand Down
27 changes: 25 additions & 2 deletions src/frontend/stellerator/service/Emulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
StartEmulationPayload,
TvEmulation,
Scaling,
ControllerType,
} from '../../elm/Stellerator/Main.elm';

import EmulationServiceInterface from '../../../web/stella/service/EmulationServiceInterface';
Expand Down Expand Up @@ -100,15 +101,32 @@ function tvMode(cartridge: Cartridge): Config.TvMode {
}
}

function controllerType(controllerType: ControllerType): Config.ControllerType {
switch (controllerType) {
case ControllerType.joystick:
return Config.ControllerType.joystick;

case ControllerType.paddles:
return Config.ControllerType.paddles;

case ControllerType.keypad:
return Config.ControllerType.keypad;

default:
throw new Error(`cannot happen: invalid controller type ${controllerType}`);
}
}

function config(cartridge: Cartridge, settings: Settings): Config {
return {
tvMode: tvMode(cartridge),
enableAudio: cartridge.volume * settings.volume > 0,
randomSeed: typeof cartridge.rngSeed === 'undefined' ? -1 : cartridge.rngSeed,
emulatePaddles: cartridge.emulatePaddles,
frameStart: typeof cartridge.firstVisibleLine === 'undefined' ? -1 : cartridge.firstVisibleLine,
pcmAudio: (cartridge.audioEmulation || settings.audioEmulation) === AudioEmulation.pcm,
cpuType: cpuType(cartridge, settings),
controllerPort0: controllerType(cartridge.controllerPort0),
controllerPort1: controllerType(cartridge.controllerPort1),
};
}

Expand Down Expand Up @@ -390,7 +408,12 @@ class Emulation {

this._driverManager
.addDriver(this._keyboardDriver, (context, driver: KeyboardDriver) =>
driver.bind(context.getJoystick(0), context.getJoystick(1), context.getControlPanel())
driver.bind(
context.getJoystick(0),
context.getJoystick(1),
context.getKeypad(0),
context.getKeypad(1),
context.getControlPanel())
)
.addDriver(this._paddleDriver, (context, driver: MouseAsPaddleDriver) => driver.bind(context.getPaddle(0)));
}
Expand Down
29 changes: 28 additions & 1 deletion src/frontend/stellerator/service/Storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
*/

import Dexie from 'dexie';
import { Cartridge, Settings, TvEmulation, Scaling } from '../../elm/Stellerator/Main.elm';
import { Cartridge, Settings, TvEmulation, Scaling, ControllerType } from '../../elm/Stellerator/Main.elm';
import { injectable } from 'inversify';

export interface CartridgeWithImage {
Expand Down Expand Up @@ -86,6 +86,33 @@ class Database extends Dexie {
delete (cartridge as any).phosphorEmulation;
});
});


this.version(3)
.stores({
cartridges: '&hash',
roms: '&hash',
settings: '&id',
})
.upgrade((transaction) => {

transaction
.table<Cartridge>('cartridges')
.toCollection()
.modify((cartridge) => {
const _cartridge = cartridge as any;

const controllerType = _cartridge.emulatePaddles ?
ControllerType.paddles :
ControllerType.joystick;

delete _cartridge.emulatePaddles;

_cartridge.controllerPort0 = controllerType;
_cartridge.controllerPort1 = controllerType;

});
});
}

cartridges: Dexie.Table<Cartridge, string>;
Expand Down
Loading