-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Adds `ControllerConnector` for the controller port attached to the CPU bus. - Adds `Controller` protocol to allow for different controller implementations. - Adds `ControlPad` as implementation of NES-004 hardware. - Adds `ShiftRegisterPISO` to handle PISO shift register of `ControlPad`. - Adds tests for all new types.
- Loading branch information
1 parent
3940493
commit 3444a9f
Showing
8 changed files
with
437 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// MIT License | ||
// | ||
// Copyright (c) 2023 Jerrod Putman | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in all | ||
// copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
// SOFTWARE. | ||
|
||
import Foundation | ||
|
||
/// A controller that can be connected to a ``ControllerConnector``. | ||
public protocol Controller: AnyObject { | ||
/// Reads a single bit from the controller. | ||
/// | ||
/// - Returns: Whether or not the most significant bit of the controller is set. | ||
func read() -> Bool | ||
|
||
/// Writes the value to the controller. | ||
/// | ||
/// - Parameter data: The data to write to the controller. | ||
func write(_ data: Value) | ||
} | ||
|
||
/// A device that allows a controller to be connected to the NES. | ||
final class ControllerConnector: AddressableReadWriteDevice { | ||
|
||
// MARK: - Initializers | ||
|
||
/// Initializes the controller connector with the specified port. | ||
init(address: Address) { | ||
self.addressRange = address...address | ||
} | ||
|
||
|
||
// MARK: - Attaching a controller to the connector | ||
|
||
/// The controller that is attached to this connector. | ||
var controller: (any Controller)? = nil | ||
|
||
|
||
// MARK: - AddressableReadWriteDevice | ||
|
||
let addressRange: AddressRange | ||
|
||
func read(from address: Address) -> Value { | ||
guard let controller else { return 0 } | ||
|
||
return controller.read() ? 1 : 0 | ||
} | ||
|
||
func write(_ data: Value, to address: Address) { | ||
controller?.write(data) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// MIT License | ||
// | ||
// Copyright (c) 2023 Jerrod Putman | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in all | ||
// copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
// SOFTWARE. | ||
|
||
import Foundation | ||
|
||
/// A class that represents an NES Control Pad (NES-004). | ||
public final class ControlPad: Controller { | ||
|
||
// MARK: - Initializers | ||
|
||
/// Initializes the control pad. | ||
public init() { | ||
self.pressedButtons = .none | ||
self.shiftRegister = .init() | ||
} | ||
|
||
|
||
// MARK: - Buttons | ||
|
||
/// An option set that represents all of the possible buttons that can be presed on the control pad. | ||
public struct Buttons: OptionSet { | ||
public var rawValue: UInt8 | ||
|
||
public static let right = Buttons(rawValue: (1 << 0)) | ||
public static let left = Buttons(rawValue: (1 << 1)) | ||
public static let down = Buttons(rawValue: (1 << 2)) | ||
public static let up = Buttons(rawValue: (1 << 3)) | ||
public static let start = Buttons(rawValue: (1 << 4)) | ||
public static let select = Buttons(rawValue: (1 << 5)) | ||
public static let b = Buttons(rawValue: (1 << 6)) | ||
public static let a = Buttons(rawValue: (1 << 7)) | ||
|
||
public static let none = Buttons([]) | ||
|
||
|
||
public init(rawValue: UInt8) { | ||
self.rawValue = rawValue | ||
} | ||
} | ||
|
||
/// The buttons that are currently pressed. | ||
public var pressedButtons: Buttons | ||
|
||
|
||
// MARK: - Controller | ||
|
||
public func read() -> Bool { | ||
shiftRegister.output() | ||
} | ||
|
||
public func write(_ data: Value) { | ||
// Move the current button state into the shift register. | ||
shiftRegister.input(pressedButtons.rawValue) | ||
} | ||
|
||
|
||
// MARK: - Private | ||
|
||
/// A shift register where the button state can be read bit-by-bit. | ||
private var shiftRegister: ShiftRegisterPISO<UInt8> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
// MIT License | ||
// | ||
// Copyright (c) 2023 Jerrod Putman | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in all | ||
// copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
// SOFTWARE. | ||
|
||
/// Represents a PISO (parallel in, serial out) shift register. | ||
struct ShiftRegisterPISO<Size: FixedWidthInteger & UnsignedInteger> { | ||
/// Initializes the shift register. | ||
/// | ||
/// - Parameter value: The initial value of the register. | ||
init(value: Size = 0) { | ||
self.value = value | ||
} | ||
|
||
/// Loads the register with a value. | ||
/// | ||
/// - Parameter value: The value. | ||
mutating func input(_ value: Size) { | ||
self.value = value | ||
} | ||
|
||
/// Outputs the value a bit at a time. | ||
mutating func output() -> Bool { | ||
// Grab the most significant bit from the shift register. | ||
// If the bit is set, then the button is being pressed. | ||
let isMSBSet = value.leadingZeroBitCount == 0 | ||
|
||
// Shift the register one bit to the left. | ||
value = value << 1 | ||
|
||
// Return the previously captured bit value. | ||
return isMSBSet | ||
} | ||
|
||
private var value: Size | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import XCTest | ||
@testable import SwiftNES | ||
|
||
final class ControlPadTests: XCTestCase { | ||
|
||
func testNoInput() { | ||
let controlPad = ControlPad() | ||
|
||
XCTAssertFalse(controlPad.read()) | ||
XCTAssertFalse(controlPad.read()) | ||
XCTAssertFalse(controlPad.read()) | ||
XCTAssertFalse(controlPad.read()) | ||
XCTAssertFalse(controlPad.read()) | ||
XCTAssertFalse(controlPad.read()) | ||
XCTAssertFalse(controlPad.read()) | ||
XCTAssertFalse(controlPad.read()) | ||
} | ||
|
||
func testInput() { | ||
let controlPad = ControlPad() | ||
|
||
controlPad.pressedButtons = [.a, .up] | ||
// This is the value that will be read. | ||
controlPad.pressedButtons = [.b, .down] | ||
|
||
// Write to the control pad to poll the current state. | ||
controlPad.write(0) | ||
|
||
// This value will be ignored. | ||
controlPad.pressedButtons = [.start] | ||
|
||
XCTAssertFalse(controlPad.read()) // A | ||
XCTAssertTrue(controlPad.read()) // B | ||
XCTAssertFalse(controlPad.read()) // Select | ||
XCTAssertFalse(controlPad.read()) // Start | ||
XCTAssertFalse(controlPad.read()) // Up | ||
XCTAssertTrue(controlPad.read()) // Down | ||
XCTAssertFalse(controlPad.read()) // Left | ||
XCTAssertFalse(controlPad.read()) // Right | ||
|
||
controlPad.pressedButtons = .none | ||
|
||
XCTAssertFalse(controlPad.read()) // A | ||
XCTAssertFalse(controlPad.read()) // B | ||
XCTAssertFalse(controlPad.read()) // Select | ||
XCTAssertFalse(controlPad.read()) // Start | ||
XCTAssertFalse(controlPad.read()) // Up | ||
XCTAssertFalse(controlPad.read()) // Down | ||
XCTAssertFalse(controlPad.read()) // Left | ||
XCTAssertFalse(controlPad.read()) // Right | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import XCTest | ||
@testable import SwiftNES | ||
|
||
final class ControllerConnectionTests: XCTestCase { | ||
|
||
func testNoController() { | ||
let connector = ControllerConnector(address: 0x1000) | ||
|
||
connector.write(1, to: 0x1000) | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // A | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // B | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Select | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Start | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Up | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Down | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Left | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Right | ||
} | ||
|
||
func testController() { | ||
let connector = ControllerConnector(address: 0x1000) | ||
|
||
let controlPad = ControlPad() | ||
connector.controller = controlPad | ||
|
||
controlPad.pressedButtons = [.a, .up] | ||
connector.write(1, to: 0x1000) | ||
XCTAssertEqual(connector.read(from: 0x1000), 1) // A | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // B | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Select | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Start | ||
XCTAssertEqual(connector.read(from: 0x1000), 1) // Up | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Down | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Left | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Right | ||
|
||
controlPad.pressedButtons = [.b] | ||
connector.write(1, to: 0x1000) | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // A | ||
XCTAssertEqual(connector.read(from: 0x1000), 1) // B | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Select | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Start | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Up | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Down | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Left | ||
XCTAssertNotEqual(connector.read(from: 0x1000), 1) // Right | ||
} | ||
} |
Oops, something went wrong.