9fans.net/go@v0.0.5/games/spacewar/spacewar.go (about)

     1  // Copyright (c) 1996 Barry Silverman, Brian Silverman, Vadim Gerasimov.
     2  // Portions Copyright (c) 2009 The Go Authors.
     3  //
     4  // Permission is hereby granted, free of charge, to any person obtaining a copy
     5  // of this software and associated documentation files (the "Software"), to deal
     6  // in the Software without restriction, including without limitation the rights
     7  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     8  // copies of the Software, and to permit persons to whom the Software is
     9  // furnished to do so, subject to the following conditions:
    10  //
    11  // The above copyright notice and this permission notice shall be included in
    12  // all copies or substantial portions of the Software.
    13  //
    14  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    15  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    16  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    17  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    18  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    19  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    20  // THE SOFTWARE.
    21  
    22  // Spacewar is the original PDP-1 Spacewar video game,
    23  // running in a PDP-1 emulator.
    24  package main // import "9fans.net/go/games/spacewar"
    25  
    26  import (
    27  	"bytes"
    28  	"image"
    29  	"log"
    30  	"os"
    31  	"time"
    32  
    33  	"9fans.net/go/draw"
    34  	"9fans.net/go/games/spacewar/pdp1"
    35  )
    36  
    37  func main() {
    38  	d, err := draw.Init(nil, "", "spacewar", "512x512")
    39  	if err != nil {
    40  		log.Fatal(err)
    41  	}
    42  
    43  	var m SpacewarPDP1
    44  	m.Init(d)
    45  	m.PC = 4
    46  	f := bytes.NewBuffer([]byte(spacewarCode))
    47  	if err = m.Load(f); err != nil {
    48  		log.Fatalf("loading %s: %s", "spacewar.lst", err)
    49  	}
    50  	for err == nil {
    51  		//fmt.Printf("step PC=%06o ", m.PC);
    52  		//fmt.Printf("inst=%06o AC=%06o IO=%06o OV=%o\n",
    53  		//	m.Mem[m.PC], m.AC, m.IO, m.OV);
    54  		err = m.Step()
    55  	}
    56  	log.Fatalf("step: %s", err)
    57  }
    58  
    59  func quitter(c <-chan bool) {
    60  	<-c
    61  	os.Exit(0)
    62  }
    63  
    64  // A SpacewarPDP1 is a PDP-1 machine configured to run Spacewar!
    65  // It responds to traps by drawing on the display, and it flushes the
    66  // display and pauses every second time the program counter reaches
    67  // instruction 02051.
    68  type SpacewarPDP1 struct {
    69  	pdp1.M
    70  	nframe    int
    71  	frameTime time.Time
    72  	disp      *draw.Display
    73  	dx, dy    int
    74  	screen    *draw.Image
    75  	ctl       pdp1.Word
    76  	kc        *draw.Keyboardctl
    77  	mc        *draw.Mousectl
    78  	cmap      []*draw.Image
    79  	pix       [][]uint8
    80  	oldpix    [][]uint8
    81  }
    82  
    83  func min(a, b int) int {
    84  	if a < b {
    85  		return a
    86  	}
    87  	return b
    88  }
    89  
    90  func (m *SpacewarPDP1) Init(d *draw.Display) {
    91  	m.disp = d
    92  	m.mc = d.InitMouse()
    93  	m.kc = d.InitKeyboard()
    94  	m.screen = d.ScreenImage
    95  	m.dx = m.screen.R.Dx()
    96  	m.dy = m.screen.R.Dy()
    97  	m.pix = make([][]uint8, m.dy)
    98  	m.oldpix = make([][]uint8, m.dy)
    99  	for i := range m.pix {
   100  		m.pix[i] = make([]uint8, m.dx)
   101  		m.oldpix[i] = make([]uint8, m.dx)
   102  	}
   103  	m.cmap = make([]*draw.Image, 256)
   104  	for i := range m.cmap {
   105  		var r, g, b draw.Color
   106  		r = draw.Color(min(0, 255))
   107  		g = draw.Color(min(i*2, 255))
   108  		b = draw.Color(min(0, 255))
   109  		m.cmap[i], _ = d.AllocImage(image.Rect(0, 0, 1, 1), d.ScreenImage.Pix, true, r<<24|g<<16|b<<8|0xff)
   110  	}
   111  	m.screen.Draw(m.screen.R, d.Black, nil, draw.ZP)
   112  }
   113  
   114  const (
   115  	frameDelay = 56 * time.Millisecond
   116  )
   117  
   118  var ctlBits = [...]pdp1.Word{
   119  	'f':  0000001,
   120  	'd':  0000002,
   121  	'a':  0000004,
   122  	's':  0000010,
   123  	'\'': 0040000,
   124  	';':  0100000,
   125  	'k':  0200000,
   126  	'l':  0400000,
   127  }
   128  
   129  func (m *SpacewarPDP1) Step() error {
   130  	if m.PC == 02051 {
   131  		m.pollInput()
   132  		m.nframe++
   133  		if m.nframe&1 == 0 {
   134  			m.flush()
   135  			t := time.Now()
   136  			if t.After(m.frameTime.Add(3 * frameDelay)) {
   137  				m.frameTime = t
   138  			} else {
   139  				m.frameTime = m.frameTime.Add(frameDelay)
   140  				for t.Before(m.frameTime) {
   141  					time.Sleep(m.frameTime.Sub(t))
   142  					t = time.Now()
   143  				}
   144  			}
   145  		}
   146  	}
   147  	return m.M.Step(m)
   148  }
   149  
   150  func (m *SpacewarPDP1) Trap(y pdp1.Word) {
   151  	switch y & 077 {
   152  	case 7:
   153  		x := int(m.AC+0400000) & 0777777
   154  		y := int(m.IO+0400000) & 0777777
   155  		x = x * m.dx / 0777777
   156  		y = y * m.dy / 0777777
   157  		if 0 <= x && x < m.dx && 0 <= y && y < m.dy {
   158  			n := uint8(min(int(m.pix[y][x])+128, 255))
   159  			m.pix[y][x] = n
   160  		}
   161  	case 011:
   162  		m.IO = m.ctl
   163  	}
   164  }
   165  
   166  func (m *SpacewarPDP1) flush() {
   167  	// Update screen image; simulate phosphor decay.
   168  	for y := 0; y < m.dy; y++ {
   169  		for x := 0; x < m.dx; x++ {
   170  			if m.oldpix[y][x] != m.pix[y][x] {
   171  				r := image.Rect(x, y, x+1, y+1)
   172  				m.screen.Draw(r, m.cmap[m.pix[y][x]], nil, draw.ZP)
   173  				m.oldpix[y][x] = m.pix[y][x]
   174  			}
   175  			m.pix[y][x] >>= 1
   176  		}
   177  	}
   178  	m.disp.Flush()
   179  }
   180  
   181  func (m *SpacewarPDP1) pollInput() {
   182  	for {
   183  		select {
   184  		case ch := <-m.kc.C:
   185  			if 0 <= ch && ch < rune(len(ctlBits)) {
   186  				m.ctl |= ctlBits[ch]
   187  			}
   188  			if 0 <= -ch && -ch < rune(len(ctlBits)) {
   189  				m.ctl &^= ctlBits[-ch]
   190  			}
   191  		default:
   192  			return
   193  		}
   194  	}
   195  }