Scalaでデジタル回路シュミレータ

Scalaでデジタル回路シュミレータ(仮)を作った。
こんな感じ。
f:id:lilyext:20170311151928p:plainf:id:lilyext:20170311151933p:plainf:id:lilyext:20170311151947p:plain
ソースコードは以下の通り。

import scala.swing._
import scala.swing.event._
import java.awt.event._
import java.awt.image.BufferedImage
import scala.collection.JavaConverters._
import java.awt.Color
import scala.collection.mutable.Map

case class VanishPartEvent(part:Part) extends Event
case class DraggingWireEvent(part:Part,port:Port) extends Event
case class ReleasedHereEvent(part:Part,port:Port) extends Event

abstract class Port{
  val point:Point=null
}
abstract class Part extends Component{
  val PortMap:Map[Port,Wire]=Map.empty[Port,Wire]
  def connect(port:Port,wire:Wire){
    PortMap(port)=wire
  }
}
trait PartObject{
  val Ports:List[Port]=null
  val distance=5
  def portNearToPoint(point:Point):Option[Port]={
    val nearport=Ports.filter(port=>(port.point.x-point.x)*(port.point.x-point.x)+(port.point.y-point.y)*(port.point.y-point.y)<distance*distance)
    if(nearport.isEmpty)return None
    else Some(nearport(0))
  }
}
object OutputTerminal extends PartObject{
  override val Ports=List[Port](Port0)
  object Port0 extends Port{
    override val point=new Point(0,5)
    override val toString="Output"
  }
}
class OutputTerminal extends Part{
  private var signal=false
  preferredSize=new Dimension(10,10)
  listenTo(mouse.clicks)
  reactions+={
    case _=>
  }
  def setSignal(sig:Boolean){
    if(sig!=signal){
      signal=sig
      repaint
    }
  }
  override def paintComponent(g:Graphics2D){
    if(signal)g.setColor(Color.GREEN)
    else g.setColor(Color.RED)
    g.fillRect(0,0,10,10)
  }
  val myself=this
  listenTo(mouse.clicks)
  listenTo(mouse.moves)
  reactions+={
    case e:MouseClicked=>{
      
      val event=e.asInstanceOf[MouseClicked]
      if(event.peer.getButton==MouseEvent.BUTTON3){
        val popupMenu=new PopupMenu{
          contents+=new MenuItem("delete"){
            listenTo(mouse.clicks)
            reactions+={
              case _:MouseReleased=>{
                
                myself.publish(VanishPartEvent(myself))
              }
            }
          }
        }
        popupMenu.show(this,event.point.x,event.point.y)
      }else if(event.peer.getButton==MouseEvent.BUTTON1){
        
      }
    }
    case MousePressed(_,p,_,_,_)=>{
      OutputTerminal.portNearToPoint(p) match{
        case Some(port)=>publish(DraggingWireEvent(this,port))
        case None=> 
      }
    }
    case MouseReleased(_,p,_,_,_)=>{
      OutputTerminal.portNearToPoint(p) match{
        case Some(port)=>publish(ReleasedHereEvent(this,port))
        case None=>
      }
    }
  }
}
object InputTerminal extends PartObject{
  override val Ports=List[Port](Port0)
  object Port0 extends Port{
    override val point=new Point(10,5)
    override val toString="Input"
  }
}
class InputTerminal extends Part{
  var signal=false
  var isCompiled=false
  preferredSize=new Dimension(10,10)

  override def paintComponent(g:Graphics2D){
    if(signal)g.setColor(Color.GREEN)
    else g.setColor(Color.RED)
    g.fillRect(0,0,10,10)
  }
  val myself=this
  listenTo(mouse.clicks)
  listenTo(mouse.moves)
  reactions+={
    case e:MouseClicked=>{
      
      val event=e.asInstanceOf[MouseClicked]
      if(event.peer.getButton==MouseEvent.BUTTON3){
        val popupMenu=new PopupMenu{
          contents+=new MenuItem("delete"){
            listenTo(mouse.clicks)
            reactions+={
              case _:MouseReleased=>{
                
                myself.publish(VanishPartEvent(myself))
              }
            }
          }
        }
        popupMenu.show(this,event.point.x,event.point.y)
      }else if(event.peer.getButton==MouseEvent.BUTTON1){
        if(isCompiled){
          signal= !signal
          repaint
          PortMap(InputTerminal.Port0).setSignal(signal)
        }
      }
    }
    case MousePressed(_,p,_,_,_)=>{
      InputTerminal.portNearToPoint(p) match{
        case Some(port)=>publish(DraggingWireEvent(this,port))
        case None=> 
      }
    }
    case MouseReleased(_,p,_,_,_)=>{
      InputTerminal.portNearToPoint(p) match{
        case Some(port)=>publish(ReleasedHereEvent(this,port))
        case None=>
      }
    }
  }
 }

object OrGatePart extends PartObject{
  
  private val OrGateImage=new BufferedImage(60,50,BufferedImage.TYPE_INT_ARGB)
  private val OrGateGraphics=OrGateImage.getGraphics
  OrGateGraphics.setColor(Color.GREEN)
  OrGateGraphics.drawArc(-25,0,50,50,-90,180)
  OrGateGraphics.drawLine(25,0,0,0)
  OrGateGraphics.drawLine(0,49,25,49)
  OrGateGraphics.drawArc(0,0,50,50,-90,180)
  override val Ports=List(Port0,Port1,Port2)
  object Port0 extends Port{
    override val point=new Point(21,12)
    override val toString="Or-Port0"
  }
  object Port1 extends Port{
    override val point=new Point(21,38)
    override val toString="Or-Port1"
  }
  object Port2 extends Port{
    override val point=new Point(50,25)
    override val toString="Or-Port2"
  }
}
class OrGatePart extends Part{
 
  preferredSize=new Dimension(50,50) 
  peer.setOpaque(true)
   override def paintComponent(g:Graphics2D){
    g.drawImage(OrGatePart.OrGateImage,0,0,null)
  }
  
  val myself=this
  listenTo(mouse.clicks)
  listenTo(mouse.moves)
  reactions+={
    case e:MouseClicked=>{
      
      val event=e.asInstanceOf[MouseClicked]
      if(event.peer.getButton==MouseEvent.BUTTON3){
        val popupMenu=new PopupMenu{
          contents+=new MenuItem("delete"){
            listenTo(mouse.clicks)
            reactions+={
              case _:MouseReleased=>{
                
                myself.publish(VanishPartEvent(myself))
              }
            }
          }
        }
        popupMenu.show(this,event.point.x,event.point.y)
      }else if(event.peer.getButton==MouseEvent.BUTTON1){
        
      }
    }
    case MousePressed(_,p,_,_,_)=>{
      OrGatePart.portNearToPoint(p) match{
        case Some(port)=>publish(DraggingWireEvent(this,port))
        case None=> 
      }
    }
    case MouseReleased(_,p,_,_,_)=>{
      OrGatePart.portNearToPoint(p) match{
        case Some(port)=>publish(ReleasedHereEvent(this,port))
        case None=>
      }
    }
  }
}
object AndGatePart extends PartObject{
  private val AndGateImage=new BufferedImage(60,50,BufferedImage.TYPE_INT_ARGB)
  private val AndGateGraphics=AndGateImage.getGraphics
  AndGateGraphics.setColor(Color.GREEN)
  private val x=Array(25,0,0,25)
  private val y=Array(0,0,49,49)
  for (i<- 0 until 3){
    AndGateGraphics.drawLine(x(i),y(i),x(i+1),y(i+1))
  }
  AndGateGraphics.drawArc(0,0,50,50,-90,180)
  override val Ports=List(Port0,Port1,Port2)
  object Port0 extends Port{
    override val point=new Point(0,12)
    override def toString="And-Port0"
  }
  object Port1 extends Port{
    override val point=new Point(0,38)
    override def toString="And-Port1"
  }
  object Port2 extends Port{
    override val point=new Point(50,25)
    override def toString="And-Port2"
  }
}
class AndGatePart extends Part {
  peer.setOpaque(true)
  preferredSize=new Dimension(50,50)
  override def paintComponent(g:Graphics2D){
    g.drawImage(AndGatePart.AndGateImage,0,0,null)
  }
  val myself=this
  listenTo(mouse.clicks)
  listenTo(mouse.moves)
  reactions+={
    case e:MouseClicked=>{
      
      val event=e.asInstanceOf[MouseClicked]
      if(event.peer.getButton==MouseEvent.BUTTON3){
        val popupMenu=new PopupMenu{
          contents+=new MenuItem("delete"){
            listenTo(mouse.clicks)
            reactions+={
              case _:MouseReleased=>{
                
                myself.publish(VanishPartEvent(myself))
              }
            }
          }
        }
        popupMenu.show(this,event.point.x,event.point.y)
      }else if(event.peer.getButton==MouseEvent.BUTTON1){
        
      }
    }
    case MousePressed(_,p,_,_,_)=>{
      AndGatePart.portNearToPoint(p) match{
        case Some(port)=>publish(DraggingWireEvent(this,port))
        case None=> 
      }
    }
    case MouseReleased(_,p,_,_,_)=>{
      AndGatePart.portNearToPoint(p) match{
        case Some(port)=>publish(ReleasedHereEvent(this,port))
        case None=>
      }
    }
    
  }
}
object InvertPart extends PartObject{
  private val InvertImage=new BufferedImage(60,50,BufferedImage.TYPE_INT_ARGB)
  private val InvertGraphics=InvertImage.getGraphics
  InvertGraphics.setColor(Color.GREEN)
  InvertGraphics.drawPolygon(Array(0,50,0,0),Array(0,25,50,0),4)
  InvertGraphics.drawOval(50,20,10,10)
  override val Ports=List(Port0,Port1)
  object Port0 extends Port{
    override val point=new Point(0,25)
    override def toString="Not-Port0"
  }
  object Port1 extends Port{
    override val point=new Point(50,25)
    override def toString="Not-Port1"
  }
}
class InvertPart extends Part{
  peer.setOpaque(true)
  preferredSize=new Dimension(60,50)
  val myself=this
  override def paintComponent(g:Graphics2D){
    g.drawImage(InvertPart.InvertImage,0,0,null)
  }
  listenTo(mouse.clicks)
  listenTo(mouse.moves)
  reactions+={
    case e:MouseClicked=>{
      
      val event=e.asInstanceOf[MouseClicked]
      if(event.peer.getButton==MouseEvent.BUTTON3){
        val popupMenu=new PopupMenu{
          contents+=new MenuItem("delete"){
            listenTo(mouse.clicks)
            reactions+={
              case _:MouseReleased=>{
               
                myself.publish(VanishPartEvent(myself))
              }
            }
          }
        }
        popupMenu.show(this,event.point.x,event.point.y)
      }else if(event.peer.getButton==MouseEvent.BUTTON1){
        
      }
    }
    case MousePressed(_,p,_,_,_)=>{
      InvertPart.portNearToPoint(p) match{
        case Some(port)=>publish(DraggingWireEvent(this,port))
        case None=> 
      }
    }
    case MouseReleased(_,p,_,_,_)=>{
      InvertPart.portNearToPoint(p) match{
        case Some(port)=>publish(ReleasedHereEvent(this,port))
        case None=>
      }
    }
  }
}

class NullPanel extends Panel{
  case class WorkItem(time:Int,action:Action)
  private type Agenda=List[WorkItem]
  private type Action= () => Unit
  private var agenda:Agenda=List()
  private var currenttime=0
  private def insert(ag:Agenda,item:WorkItem):Agenda={
    if(ag.isEmpty || item.time<ag.head.time)item::ag
    else ag.head::insert(ag.tail,item)
  }
  def initSimulation={
    agenda=List()
    currenttime=0
  }
  def afterDelay(delay:Int)(block : =>Unit){
    val item=WorkItem(currenttime+delay,()=>block)
    agenda=insert(agenda,item)
  }
  def next{
    agenda match{
      case WorkItem(time,action)::rest=>{
        agenda=rest
        currenttime=time
        action()
      }
      case List()=>
    }
  }
  def run(){
    afterDelay(0)({})
    while(!agenda.isEmpty)next
  }
  def inverter(input:Wire,output:Wire){
    def invertAction(){
      val sig=input.getSignal
      afterDelay(10){output setSignal !sig}
    }
    input addAction invertAction
  }
  def andGate(input0:Wire,input1:Wire,output:Wire){
    def andAction(){
      val sig0=input0.getSignal
      val sig1=input1.getSignal
      afterDelay(10){output setSignal(sig0 & sig1)}
    }
    input0 addAction andAction
    input1 addAction andAction
  }
  def orGate(input0:Wire,input1:Wire,output:Wire){
    def orAction(){
      val sig0=input0.getSignal
      val sig1=input1.getSignal
      afterDelay(10){output.setSignal(sig0 | sig1)}
    }
    input0 addAction orAction
    input1 addAction orAction
  }
  def input(terminal:InputTerminal){
    terminal.isCompiled=true
    def inputAction(){
      val sig=terminal.signal
      afterDelay(0){terminal.PortMap(InputTerminal.Port0).setSignal(sig)}
    }
    terminal.PortMap(InputTerminal.Port0) addAction inputAction
  }
  def output(terminal:OutputTerminal){
    def outputAction(){
      val sig=terminal.PortMap(OutputTerminal.Port0).getSignal
      afterDelay(0){terminal.setSignal(sig)}
    }
    terminal.PortMap(OutputTerminal.Port0) addAction outputAction
  }
  def compile={
    wirelist.foreach(wire=>{
      wire.terminal(0)._1.connect(wire.terminal(0)._2, wire)
      wire.terminal(1)._1.connect(wire.terminal(1)._2, wire)
    })
    try{
      partlist.foreach(_ match{
        case part:AndGatePart=>{
          andGate(part.PortMap(AndGatePart.Port0),part.PortMap(AndGatePart.Port1),part.PortMap(AndGatePart.Port2))
        }
        case part:OrGatePart=>{
          orGate(part.PortMap(OrGatePart.Port0),part.PortMap(OrGatePart.Port1),part.PortMap(OrGatePart.Port2))
        }
        case part:InvertPart=>{
          inverter(part.PortMap(InvertPart.Port0),part.PortMap(InvertPart.Port1))
        }
        case terminal:InputTerminal=>{
          input(terminal)
        }
        case terminal:OutputTerminal=>{
          output(terminal)
        }
      })
      run
    }catch{
      case e:java.util.NoSuchElementException=>{
        println(e)
        println("failed")
        initSimulation
        wirelist.foreach(_.removeAllAction)
        
      }
    }
  }
  peer.setLayout(null)
  preferredSize=new Dimension(500,500)
  val imgBuffer=new BufferedImage(500,500,BufferedImage.TYPE_INT_RGB)
  implicit def DoubleToInt(d: Double): Int = d.toInt
  val movingBuffer=new BufferedImage(500,500,BufferedImage.TYPE_INT_RGB)
  val mg=movingBuffer.getGraphics
  val bg=imgBuffer.getGraphics
  var partlist:List[Part]=Nil
  var wirelist:List[Wire]=Nil
  var isWiring=false
  var tmpWiringWire:Wire=_
  listenTo(this)
  listenTo(mouse.moves)
  listenTo(mouse.clicks)
  def output={
    wirelist.foreach(println)
  }
    reactions+={
      case e:MouseClicked=>{
        val event=e.asInstanceOf[MouseClicked]
        if(event.peer.getButton==MouseEvent.BUTTON3){
          val popupMenu:PopupMenu=new PopupMenu{
            contents+=new MenuItem("invert"){
              listenTo(mouse.clicks)
              reactions+={
                case _:MouseReleased=>{
                  val part=new InvertPart
                  addPart(part,event.point.x,event.point.y)
                }
              }
            }
            contents+=new MenuItem("and"){
              listenTo(mouse.clicks)
              reactions+={
                case _:MouseReleased=>{
                  addPart(new AndGatePart,event.point.x,event.point.y)
                }
              }
            }
            contents+=new MenuItem("or"){
              listenTo(mouse.clicks)
              reactions+={
                case _:MouseReleased=>{
                  addPart(new OrGatePart,event.point.x,event.point.y)
                }
              }
            }
            contents+=new MenuItem("input"){
              listenTo(mouse.clicks)
              reactions+={
                case _:MouseReleased=>{
                  addPart(new InputTerminal,event.point.x,event.point.y)
                }
              }
            }
            contents+=new MenuItem("output"){
              listenTo(mouse.clicks)
              reactions+={
                case _:MouseReleased=>{
                  addPart(new OutputTerminal,event.point.x,event.point.y)
                }
              }
            }
          }
          popupMenu.show(this,event.point.x,event.point.y)
        }else if(event.peer.getButton==MouseEvent.BUTTON1){
          if(isWiring)finishWiring
        }
        
      }
      case MouseMoved(_,p,_)=>{
        if(isWiring){
          mg.drawImage(imgBuffer,0,0,null)
          val locateOfPart0=tmpWiringWire.terminal(0)._1.peer.getLocation()
          val offsetOfTerminal0=tmpWiringWire.terminal(0)._2.point
          mg.drawLine(locateOfPart0.getX+offsetOfTerminal0.getX,locateOfPart0.getY+offsetOfTerminal0.getY,p.getX,p.getY)
          repaint
        }
      }
      case DraggingWireEvent(part,port)=>{
        if(!isWiring){
        isWiring=true
        tmpWiringWire=new Wire
        tmpWiringWire.terminal(0)=(part,port)
        }
      }
      case ReleasedHereEvent(part,port)=>{
        if(isWiring){
          if(tmpWiringWire.terminal(0)._1!=part){
            finishWiring
            tmpWiringWire.terminal(1)=(part,port)
            wirelist=tmpWiringWire::wirelist
            addWireToPanel(tmpWiringWire)
            repaint
          }
        }
      }
      case VanishPartEvent(p)=>{
        peer.remove(p.peer)
        partlist=partlist.filter(p!=)
        wirelist=wirelist.filter(wire=>wire.terminal(0)._1!=p && wire.terminal(1)._1!=p)
        bg.setColor(Color.BLACK)
        bg.fillRect(0,0,imgBuffer.getWidth,imgBuffer.getHeight)
        wirelist.foreach(addWireToPanel(_))
        repaint
      }
      case _:MouseExited=>{
        //if(isWiring)isWiring=false
      }
      
    }
  def finishWiring={
    isWiring=false
    repaint
  }
  def addWireToPanel(wire:Wire)={
    val locateOfPart0=wire.terminal(0)._1.peer.getLocation()
    val offsetOfTerminal0=wire.terminal(0)._2.point
    val locateOfPart1=wire.terminal(1)._1.peer.getLocation()
    val offsetOfTerminal1=wire.terminal(1)._2.point
    bg.setColor(Color.GREEN)
    bg.drawLine(locateOfPart0.getX+offsetOfTerminal0.getX,locateOfPart0.getY+offsetOfTerminal0.getY,locateOfPart1.getX+offsetOfTerminal1.getX,locateOfPart1.getY+offsetOfTerminal1.getY)
  }
  override def paintComponent(g:Graphics2D){
    if(isWiring)g.drawImage(movingBuffer,0,0,null)
    else g.drawImage(imgBuffer,0,0,null)
  }
  def addPart(comp:Part,x:Int,y:Int):Unit={
    val p =comp.peer
    partlist=comp::partlist
    p.setLocation(x,y)
    p.setSize(p.getPreferredSize)
    peer.add(p)
    repaint()
    listenTo(comp)
    
  }
  def remove(comp:Component):Unit={
    peer.remove(comp.peer)
  }

}
class Wire {
  val terminal=new Array[(Part,Port)](2)
  private var sigVal=false
  private var actions:List[Action]=List()
  private type Action = () =>Unit
  def getSignal=sigVal
  def setSignal(s:Boolean)={
    if(s!=sigVal){
      sigVal=s
      actions.foreach(action=>action())
    }
  }
  def addAction(a:Action)={
    actions=a::actions
    a()
  }
  def removeAllAction={
    sigVal=false
    actions=List()
  }
  override def toString:String=terminal(0)._2.toString+"--"+terminal(1)._2.toString  
}


class TestFrame extends MainFrame{
  title="Null Test"
  resizable=false
  val board=new NullPanel
  contents=new BoxPanel(Orientation.Vertical){
    contents+=board
    contents+=new BoxPanel(Orientation.Horizontal){
      contents+=Button("Compile"){
         board.output
         board.compile
      }
    }
  }
  
}
object Main {
  def main(args:Array[String]){
    val ui=new TestFrame
    ui.visible=true
  }
}

かなり雑なので推敲したい。

以下のような機能も追加したい

  • 配線を分岐出来る機能
  • 配線を曲げることが出来るように
  • 複数のパーツをひとまとめに出来るように

これを参考にした。
http://www.scala-lang.org/docu/files/ScalaByExample.pdf

というかUI部分以外の論理回路の実装はほぼパクリ……